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

#include "ll.h"

static LL_PREFIX_ll_item_t * LL_PREFIX_ll_getitem(LL_PREFIX_ll_t *list);
static int LL_PREFIX_ll_grow(LL_PREFIX_ll_t *list);

LL_PREFIX_ll_item_t *
LL_PREFIX_ll_append(LL_PREFIX_ll_t *list, LL_DATA_TYPE *data)
{
	LL_PREFIX_ll_item_t *new_item;

	if ( (new_item = LL_PREFIX_ll_getitem(list)) == '\0')
		return '\0';

	memcpy(&(new_item->data), data, sizeof(LL_DATA_TYPE));
	new_item->next = '\0';

	if (list->tail == '\0')
	{
		list->tail = new_item;
		list->head = new_item;
		list->item_count++;
	}
	else
	{
		list->tail->next = new_item;
		list->tail = new_item;
		list->item_count++;
	}

	return new_item;
}

LL_PREFIX_ll_t *
LL_PREFIX_ll_append_list(LL_PREFIX_ll_t *dest, LL_PREFIX_ll_t *source)
{
	LL_PREFIX_ll_item_t *cur_item;

	for (cur_item = LL_PREFIX_ll_head(source); cur_item != '\0';
	     cur_item = LL_PREFIX_ll_next(cur_item))
	{
		if (LL_PREFIX_ll_append(dest, LL_PREFIX_ll_data(cur_item)) == '\0')
			return '\0';
	}

	return dest;
}

void
LL_PREFIX_ll_free(LL_PREFIX_ll_t *list)
{
	LL_PREFIX_ll_items_t *current_block;
	LL_PREFIX_ll_items_t *next_block;

	if (list->first_block != '\0')
	{
		current_block = list->first_block;

		do
		{
			next_block = current_block->next;
			free(current_block->items);
			free(current_block);
			current_block = next_block;
		} while (current_block != '\0');
	}

	free(list);
}

static LL_PREFIX_ll_item_t *
LL_PREFIX_ll_getitem(LL_PREFIX_ll_t *list)
{
   LL_PREFIX_ll_item_t *item;

   if (list->free_list == '\0')
   {
      if (LL_PREFIX_ll_grow(list) == 0)
         return '\0';
   }

   item = list->free_list;
   list->free_list = list->free_list->next;
   bzero((void *) item, sizeof(LL_PREFIX_ll_item_t));

   return item;
}

static int
LL_PREFIX_ll_grow(LL_PREFIX_ll_t *list)
{
	int i;
	LL_PREFIX_ll_items_t *current_block;

	if (list->current_block == '\0')
	{
		if ( (list->current_block = (LL_PREFIX_ll_items_t *)
		                            malloc(sizeof(LL_PREFIX_ll_items_t))) == '\0')
			return 0;

		current_block = list->current_block;
		list->first_block = current_block;
	}
	else
	{
		if ( (list->current_block->next = (LL_PREFIX_ll_items_t *)
		                                   malloc(sizeof(LL_PREFIX_ll_items_t))) == '\0')
		return 0;

		current_block = list->current_block->next;
	}

	if ( (current_block->items = (LL_PREFIX_ll_item_t *)
		                  calloc(list->increment, sizeof(LL_PREFIX_ll_item_t))) == '\0')
	{
		free(current_block);
		return 0;
	}

	list->current_block = current_block;
	list->current_block->next = '\0';

	for (i = 0; i < (list->increment - 1); i++)
		list->current_block->items[i].next =
		                                     &(list->current_block->items[i + 1]);

	list->current_block->items[list->increment - 1].next = '\0';
	list->free_list = list->current_block->items;

	return 1;
}

LL_PREFIX_ll_t *
LL_PREFIX_ll_init(unsigned int initial, unsigned int increment)
{
	LL_PREFIX_ll_t *new_list;
	unsigned int i;

	if ( (new_list = (LL_PREFIX_ll_t *) malloc(sizeof(LL_PREFIX_ll_t))) == '\0')
		return '\0';

	bzero((void *) new_list, sizeof(LL_PREFIX_ll_t));
	new_list->initial = initial;
	new_list->increment = increment;

	if (initial > 0)
	{
		if ( (new_list->first_block = (LL_PREFIX_ll_items_t *)
		                          malloc(sizeof(LL_PREFIX_ll_items_t))) == '\0')
		{
			free(new_list);
			return '\0';
		}

		new_list->first_block->next = '\0';

		if ( (new_list->first_block->items = (LL_PREFIX_ll_item_t *)
		                           calloc(initial, sizeof(LL_PREFIX_ll_item_t))) == '\0')
		{
			free(new_list->first_block);
			free(new_list);
			return '\0';
		}

		for (i = 0; i < (initial - 1); i++)
		   new_list->first_block->items[i].next =
                                         &(new_list->first_block->items[i + 1]);

		new_list->first_block->items[initial - 1].next = '\0';
		new_list->free_list = new_list->first_block->items;
		new_list->current_block = new_list->first_block;
	}

	return new_list;
}
