/**
 * @file list.c
 *
 * @brief List functions.
 *
 * Copyright 2007-2008, Freie Universitaet Berlin. All rights reserved.
 * Please refer to the COPYING file for detailed licensing information.
 *
 * @author Georg Wittenburg
 *
 * $Id: list.c 2685 2008-06-25 16:38:56Z wittenbu $
 */

#include "../types.h"
#include "../vars.h"
#include "../interface.h"
#include "../interface_internal.h"
#include "debug.h"
#include "list.h"



// Disable consistency checks.
#undef C_ENABLE_DEBUG

#ifdef C_ENABLE_DEBUG
/**
 * @brief Check the consistency of a list data structure.
 *
 * @param list Pointer to a list to check.
 */
static void list_check_consistency(const list_t* list) {
	int index = 0;
	_list_item_t* item = list->first_item;
	while(item != NULL) {
		if(item->data == NULL) {
			C_DEBUG(C_DEBUG_LEVEL_FATAL, "List consistency check of list at %p failed; NULL data item for item #%d at %p.\n", list, index, item);
			abort();
		}
		item = item->next;
		index++;
	}
	if(index != list->size) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "List consistency check of list at %p failed; size mismatch (%d != %d)\n", index, list->size);
		abort();
	}
}
#endif	// C_ENABLE_DEBUG

/**
 * @brief Allocate and initialize a new list.
 *
 * @param list Pointer to a pointer to a (uninitialized) list.
 */
void list_alloc(list_t** list) {
	MALLOC(*list, list_t);
	(*list)->first_item = NULL;
	(*list)->last_item = NULL;
	(*list)->size = 0;
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "New list allocated at %p.\n", *list);
}

/**
 * @brief Free a list and a its elements and set the list pointer to NULL.
 *
 * @param list Pointer to pointer to the list to free.
 */
void list_free(list_t** list) {
	_list_item_t* item;

	if(*list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to free list at NULL...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(*list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Freeing list at %p...\n", *list);
	item = (*list)->first_item;
	while(item != NULL) {
		_list_item_t* tmp = item->next;
		FREE(item->data);
		FREE(item);
		item = tmp;
	}
	FREE(*list);
}

/**
 * @brief Add a new data item to a list.
 *
 * @param list Pointer to the list.
 * @param data Pointer to the data item.
 */
void list_add(list_t* list, void* data) {
	_list_item_t* item;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to add item to list at NULL...\n");
		abort();
	} else if(data == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to add NULL data item to list at %p...\n", list);
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Adding new data item at %p to list at %p; %d item(s) in list...\n", data, list, list->size + 1);
	MALLOC(item, _list_item_t);
	item->data = data;
	item->next = NULL;
	if(list->size == 0)
		list->first_item = item;
	else
		list->last_item->next = item;
	list->last_item = item;
	list->size++;

#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
}

/**
 * @brief Insert a new data item at a specific location of a list.
 *
 * The list is traversed from beginning to end and for each data item the
 * boolean parameter function is called. The new data item is inserted into
 * the list before the first element for which the parameter function becomes
 * <code>true</code>. If the parameter function is <code>false</code> for all
 * elements of the list, the new data item is inserted at the end of the list.
 *
 * @param list Pointer to the list.
 * @param data Pointer to the data item.
 * @param matches Function used for finding the correct location.
 */
void list_insert_where(list_t* list, void* data, bool(*matches)(const void*, const void*)) {
	_list_item_t* item;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to insert data item into list at NULL...\n");
		abort();
	} else if(data == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to insert NULL data item to list at %p...\n", list);
		abort();
	} else if(matches == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to insert data item into list at %p with NULL function...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Inserting new data item at %p into list at %p; %d item(s) in list...\n", data, list, list->size + 1);
	MALLOC(item, _list_item_t);
	item->data = data;
	if(list->size == 0) {
		item->next = NULL;
		list->first_item = item;
		list->last_item = item;
	} else if(list->size == 1) {
		if(matches(list->first_item->data, data)) {
			item->next = list->first_item;
			list->first_item = item;
		} else {
			item->next = NULL;
			list->first_item->next = item;
			list->last_item = item;
		}
	} else if(matches(list->first_item->data, data)) {
		item->next = list->first_item;
		list->first_item = item;
	} else if(!matches(list->last_item->data, data)) {
		item->next = NULL;
		list->last_item->next = item;
		list->last_item = item;
	} else {
		_list_item_t* current_item = list->first_item;
		_list_item_t* previous_item = NULL;
		while(!matches(current_item->data, data)) {
			previous_item = current_item;
			current_item = current_item->next;
		}
		previous_item->next = item;
		item->next = current_item;
	}
	list->size++;

#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
}

/**
 * @brief Remove a data item from the list.
 *
 * @param list Pointer to the list.
 * @param data Pointer to the data item.
 */
void list_remove(list_t* list, const void* data) {
	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to remove data item from list at NULL...\n");
		abort();
	} else if(data == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to remove NULL data item from list at %p...\n", list);
		abort();
	} else if(list->size == 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to remove data item from empty list...\n", list);
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	if(list->size == 1) {
		if(list->first_item->data == data) {
			C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Removing data item at %p from list at %p; %d item(s) in list...\n", data, list, list->size - 1);
			free(list->first_item);
			list->first_item = NULL;
			list->last_item = NULL;
			list->size = 0;
#ifdef C_ENABLE_DEBUG
			list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
			return;
		}
	} else if(list->first_item->data == data) {
		_list_item_t* tmp;
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Removing data item at %p from list at %p; %d item(s) in list...\n", data, list, list->size - 1);
		tmp = list->first_item;
		list->first_item = list->first_item->next;
		free(tmp);
		list->size--;
#ifdef C_ENABLE_DEBUG
		list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
		return;
	} else if(list->last_item->data == data) {
		_list_item_t* item;
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Removing data item at %p from list at %p; %d item(s) in list...\n", data, list, list->size - 1);
		item = list->first_item;
		while(item->next != list->last_item)
			item = item->next;
		free(list->last_item);
		list->last_item = item;
		list->last_item->next = NULL;
		list->size--;
#ifdef C_ENABLE_DEBUG
		list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
		return;
	} else {
		_list_item_t* current_item = list->first_item;
		_list_item_t* previous_item = NULL;
		while(current_item != NULL) {
			if(current_item->data == data) {
				C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Removing data item at %p from list at %p; %d item(s) in list...\n", data, list, list->size - 1);
				previous_item->next = current_item->next;
				free(current_item);
				list->size--;
#ifdef C_ENABLE_DEBUG
				list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
				return;
			}
			previous_item = current_item;
			current_item = current_item->next;
		}
	}
	C_DEBUG(C_DEBUG_LEVEL_ERROR, "Trying to remove data item at %p that is not list at %p...\n", data, list);
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
}

/**
 * @brief Check whether a list contains a specific data item.
 *
 * @param list Pointer to the list.
 * @param data Pointer to the data item.
 * @return <code>true</code> if the data item is contained in the list,
 * <code>false</code> otherwise.
 */
bool list_contains(const list_t* list, const void* data) {
	_list_item_t* current_item;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to check for data item at %p of list at NULL...\n", data);
		abort();
	} else if(data == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to check for data item at NULL of list at %p...\n", list);
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Checking for data item at %p in list at %p...\n", data, list);

	current_item = list->first_item;
	while(current_item != NULL) {
		if(current_item->data == data) {
			C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Found data item at %p in list at %p...\n", current_item->data, list);
			return true;
		}
		current_item = current_item->next;
	}
	return false;
}

/**
 * @brief Get a data item at a specific index of a list.
 *
 * The first item of the list has index 0.
 *
 * @param list Pointer to the list.
 * @param index Index of the data item to retrieve.
 * @return Pointer to the data item at the specified index of the list.
 */
void* list_get(const list_t* list, unsigned int index) {
	_list_item_t* item;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve data item #%d of list at NULL...\n", index);
		abort();
	} else if(index >= list_size(list)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve data item #%d of list at %p, but list has only %d elements...\n", index, list, list_size(list));
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Retrieving data item #%d of list at %p...\n", index, list);

	item = list->first_item;
	while(index-- > 0) {
		item = item->next;
	}
	return item->data;
}

/**
 * @brief Get the size of a list.
 *
 * @param list Pointer to the list.
 * @return Size of the list.
 */
unsigned int list_size(const list_t* list) {
	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve size of list at NULL...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "List at %p has %d data items.\n", list, list->size);
	return list->size;
}

/**
 * @brief Check whether a list is empty.
 *
 * @param list Pointer to the list.
 * @return <code>true</code> if the list doesn't contain any elements,
 * <code>false</code> otherwise.
 */
bool list_empty(const list_t* list) {
	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to check whether list at NULL is empty...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	if(list->size == 0) {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "List at %p is empty.\n", list);
		return true;
	} else {
		C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "List at %p is not empty.\n", list);
		return false;
	}
}

/**
 * @brief Return the first data item of a list.
 *
 * @param list Pointer to the list.
 * @return Pointer to the first data item of the list.
 */
void* list_head(const list_t* list) {
	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve first data item of list at NULL...\n");
		abort();
	} else if(list_empty(list)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve first data item of empty list at %p...\n", list);
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Returning first data item %p in list at %p...\n", list->first_item->data, list);
	return list->first_item->data;
}

/**
 * @brief Return the last data item of a list.
 *
 * @param list Pointer to the list.
 * @return Pointer to the last data item of the list.
 */
void* list_tail(const list_t* list) {
	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve last data item of list at NULL...\n");
		abort();
	} else if(list_empty(list)) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to retrieve last data item of empty list at %p...\n", list);
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Returning last data item %p in list at %p...\n", list->last_item->data, list);
	return list->last_item->data;
}

/**
 * @brief Find a specific data item in a list.
 *
 * The list is traversed from beginning to end and for each data item the
 * boolean parameter function is called. A pointer to the first data item for
 * which the function becomes <code>true</code> is returned. If the parameter
 * function is <code>false</code> for all data items of the list, this function
 * returns NULL.
 *
 * @param list Pointer to the list.
 * @param data Pointer to the data item.
 * @param matches Function used for finding a matching data item.
 * @return Pointer to a matching data item or NULL if no item matches.
 */
void* list_find(const list_t* list, const void* data, bool(*matches)(const void*, const void*)) {
	_list_item_t* current_item;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to find data item in list at NULL...\n");
		abort();
	} else if(matches == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to find data item in list at %p with NULL function...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	current_item = list->first_item;
	while(current_item != NULL) {
		if(matches(current_item->data, data)) {
			C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "Found data item at %p in list at %p...\n", current_item->data, list);
			return current_item->data;
		}
		current_item = current_item->next;
	}
	C_DEBUG(C_DEBUG_LEVEL_VERBOSE, "No matching data item found in list at %p...\n", list);
	return NULL;
}

/**
 * @brief Print a list.
 *
 * @param list Pointer to the list.
 * @param print_data Function used for printing data items.
 */
void list_print(const list_t* list, void(*print_data)(const void*)) {
	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to print list at NULL...\n");
		abort();
	} else if(print_data == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to print list at %p with NULL function...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	C_DEBUG(C_DEBUG_LEVEL_NONE, "[");
	if(list->size != 0) {
		_list_item_t* current_item = list->first_item;
		print_data(current_item->data);
		current_item = current_item->next;
		while(current_item != NULL) {
			C_DEBUG(C_DEBUG_LEVEL_NONE, ", ");
			print_data(current_item->data);
			current_item = current_item->next;
		}
	}
	C_DEBUG(C_DEBUG_LEVEL_NONE, "]");
}

/**
 * @brief Apply a function to all data items in a list.
 *
 * @param list Pointer to the list.
 * @param function Function to apply on data items.
 */
void list_map(const list_t* list, void(*function)(const void*)) {
	_list_item_t* current_item;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to apply function to list at NULL...\n");
		abort();
	} else if(function == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to apply NULL function to list at %p...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	current_item = list->first_item;
	while(current_item != NULL) {
		function(current_item->data);
		current_item = current_item->next;
	}
}

/**
 * @brief Sort a list using a given function for comparisson.
 *
 * The algorithm used for sorting is BubbleSort, because the list
 * implementation is single-linked.
 *
 * @param list Pointer to the list.
 * @param less_than Function to use for comparing data items.
 */
void list_sort(list_t* list, bool(*less_than)(const void*, const void*)) {
	unsigned int i;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to sort list at NULL...\n");
		abort();
	} else if(less_than == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to sort list at %p with NULL function...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	for(i = list->size - 1; i > 0; i--) {
		_list_item_t* current_item = list->first_item;
		unsigned int j;
		for(j = 0; j < i; j++) {
			_list_item_t* next_item = current_item->next;
			if(less_than(next_item->data, current_item->data)) {
				void* tmp_data = current_item->data;
				current_item->data = next_item->data;
				next_item->data = tmp_data;
			}
			current_item = current_item->next;
		}
	}

#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG
}

/**
 * @brief Convert a list into an array.
 *
 * @param list Pointer to the list.
 * @param array Pointer to the array.
 * @param data_item_size Size of the data items of the list in bytes.
 */
void list_to_array(const list_t* list, void* array, size_t data_item_size) {
	_list_item_t* current_item;
	char* position = (char*) array;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to convert list at NULL into array...\n");
		abort();
	} else if(array == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to convert list at %p into array at NULL...\n");
		abort();
	} else if(data_item_size == 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to convert list at %p into array, but data item size is 0...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	current_item = list->first_item;
	while(current_item != NULL) {
		memcpy(position, current_item->data, data_item_size);
		position += data_item_size;
		current_item = current_item->next;
	}
}

/**
 * @brief Fill a list with the content of an array.
 *
 * @param list Pointer to the list.
 * @param array Pointer to the array.
 * @param data_item_size Size of the data items of the list in bytes.
 * @param array_size Size of the array in bytes.
 */
void list_from_array(list_t* list, const void* array, size_t data_item_size, size_t array_size) {
	char* position = NULL;

	if(list == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to fill list at NULL from array...\n");
		abort();
	} else if(array == NULL) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to fill list at %p from array at NULL...\n");
		abort();
	} else if(data_item_size == 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to fill list at %p from array, but data item size is 0...\n");
		abort();
	} else if((array_size % data_item_size) != 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to fill list at %p from array, but array size is not a multiple of data_item_size (%d != %d)...\n", array_size, data_item_size);
		abort();
	} else if(list->size != 0) {
		C_DEBUG(C_DEBUG_LEVEL_FATAL, "Trying to fill list at %p from array, but data list is not empty...\n");
		abort();
	}
#ifdef C_ENABLE_DEBUG
	list_check_consistency(list);
#endif	// C_ENABLE_DEBUG

	for(position = (char*) array; position < (((char*) array) + array_size); position += data_item_size) {
		char* p = NULL;
		MALLOC_ARRAY(p, char, data_item_size);
		memcpy(p, position, data_item_size);
		list_add(list, p);
	}
}
