/* **********************************************
 * generic linked list in plain c
 * author: Freddy Hurkmans
 * date: dec 20, 2014
 * **********************************************/

/* ******************************************************************************
 * This list is designed using object oriented ideas, but is implemented in c.
 * 
 * The idea is: you construct a list for a certain data structure (say: mystruct):
 * list_admin* mylist = construct_list(sizeof(mystruct))
 * mylist contains private data for this list implementation. You need not worry
 * about it, only give it as first parameter to each list method.
 * 
 * This means you can have multiple lists at the same time, just make sure you
 * give the right list_admin structure to the list function.
 * When you're done with your list, just call the destructor: destruct_list(&mylist)
 * The destructor automatically deletes remaining list elements and the list administration.
 * 
 * As this list implementation keeps its own administration in list_admin, you need
 * not worry about next pointers and such. Your structure doesn't even need pointers
 * to itself, you only need to worry about data. A valid struct could thus be:
 * typedef struct
 * {
 *     int  nr;
 *     char text[100];   
 * } mystruct;
 * 
 * Adding data to the list is done using list_add_* methods, such as list_add_tail,
 * which adds data to the end of the list:
 * mystruct data = {10, "hello world!"};
 * int result = list_add_tail(mylist, &data);
 * You don't have to provide the size of your struct, you've already done that in
 * the destructor. If result < 0 list_add_* has failed (either a parameter problem
 * or malloc didn't give memory).
 * 
 * You can get a pointer to your data using list_get_*, e.g. get the 2nd element:
 * mystruct* dataptr = list_get_element(admin, 1);
 * or get the last element:
 * mystruct* dataptr = list_get_last(admin);
 * If dataptr is not NULL it points to the correct data, else the operation failed.
 * 
 * Searching for data in your list can be done with list_index_of*. list_index_of
 * compares the data in each list item to the given data. If they are the same, it
 * returns the index. If you want to search for an element with a certain value for
 * nr or text (see mystruct) you can implement your own compare function and use
 * list_index_of_special (check memcmp for required return values):
 * int mycompare(void* p1, void* p2, size_t size)
 * {
 *     // this example doesn't need size!
 *     mystruct* org = (mystruct*)p1;
 *     int* nr = (int*)p2;
 *     return org->nr - nr; // return 0 if they are the same
 * }
 * Say you want to search for an element that has nr 10:
 * int nr = 10;
 * int index = list_index_of_special(mylist, &nr, mycompare);
 * As noted earlier: mycompare must have the same prototype as memcmp. In fact:
 * list_index_of is a shortcut for list_index_of_special(mylist, data, memcmp).
 * 
 * Finally you can delete items with list_delete_*. They should work rather
 * straight forward. If they succeed they return 0. You don't have to delete
 * all items before destructing your list.
 * 
 * ******************************************************************************/


#include <string.h> /* for memcmp and memcpy */

#include "list.h"

static size_t data_size(const list_admin* admin);
static list_head* get_guaranteed_list_head_of_element_n(list_admin* admin, size_t index);
static list_head* get_list_head_of_element_n(list_admin* admin, size_t index);
static void remove_first_element(list_admin* admin);
static int remove_non_first_element(list_admin* admin, size_t index);

/* **********************************************
 * Constructor / destructor
 * **********************************************/
list_admin* construct_list(size_t element_size)
{
    list_admin* admin = malloc(sizeof(*admin));
    if (admin != NULL)
    {
        admin->head = NULL;
        admin->element_size = element_size;
        admin->nr_elements = 0;
    }
    return admin;
}

void destruct_list(list_admin** admin)
{
    if ((admin != NULL) && (*admin != NULL))
    {
        while ((*admin)->head != NULL)
        {
            list_head* temp = (*admin)->head;
            (*admin)->head = (*admin)->head->next;
            free(temp);
            temp = NULL;
        }
        free(*admin);
        *admin = NULL;
    }
}

/* **********************************************
 * Public functions
 * **********************************************/
int list_get_nr_elements(list_admin* admin)
{
    int nr_elements = -1;
    if (admin != NULL)
    {
        nr_elements = admin->nr_elements;
    }
    return nr_elements;
}

int list_add_tail(list_admin* admin, const void* data)
{
    int result = -1;
    if ((admin != NULL) && (data != NULL))
    {
        list_head* new = malloc(data_size(admin));
        if (new != NULL)
        {
            result = 0;
            new->next = NULL;
            memcpy(new+1, data, admin->element_size);

            if (admin->head == NULL)
            {
                admin->head = new;
            }
            else
            {
                list_head* temp = get_guaranteed_list_head_of_element_n(admin, admin->nr_elements-1);
                temp->next = new;
            }

            admin->nr_elements++;
        }
    }
    return result;
}


void* list_get_element(list_admin* admin, size_t index)
{
    list_head* item = get_list_head_of_element_n(admin, index);
    if(item != NULL)
    {
        item++; // user data is just beyond list_head, thus we must increase the pointer
    }
    return item;
}

void* list_get_last(list_admin* admin)
{
    list_head* item = NULL;
    if (admin != NULL)
    {
        item = list_get_element(admin, admin->nr_elements-1);
    }
    return item;
}

int list_index_of(list_admin* admin, const void* data)
{
    return list_index_of_special(admin, data, memcmp);
}

int list_index_of_special(list_admin* admin, const void* data, CompareFuncPtr compare)
{
    int index = -1;
    if ((admin != NULL) && (data != NULL) && (compare != NULL))
    {
        list_head* temp = admin->head;
        index = 0;
        while ((temp != NULL) && (compare(temp+1, data, admin->element_size) != 0))
        {
            temp = temp->next;
            index++;
        }
        if (temp == NULL)
        {
            index = -1;
        }
    }
    return index;
}

int list_delete_item(list_admin* admin, size_t index)
{
    int result = -1;
    if ((admin != NULL) && (admin->head != NULL) && (index < admin->nr_elements))
    {
        if (index == 0)
        {
            result = 0;
            remove_first_element(admin);
        }
        else
        {
            result = remove_non_first_element(admin, index);
        }
    }
    return result;    
}

int list_delete_last(list_admin* admin)
{
    int result = -1;
    if (admin != NULL)
    {
        result = list_delete_item(admin, admin->nr_elements - 1);
    }
    return result;
}

/* **********************************************
 * Private functions
 * **********************************************/
static size_t data_size(const list_admin* admin)
{
    return admin->element_size + sizeof(list_head);
}

static list_head* get_guaranteed_list_head_of_element_n(list_admin* admin, size_t index)
{
    list_head* item = admin->head;
    for (size_t i = 0; (i < index) && (item != NULL); i++)
    {
        item = item->next;
    }
    return item;
}

static list_head* get_list_head_of_element_n(list_admin* admin, size_t index)
{
    list_head* item = NULL;
    if ((admin != NULL) && (index < admin->nr_elements))
    {
        item = get_guaranteed_list_head_of_element_n(admin, index);
    }
    return item;
}

static void remove_first_element(list_admin* admin)
{
    list_head* temp = admin->head;
    admin->head = admin->head->next;
    free(temp);
    temp = NULL;
    admin->nr_elements--;
}

static int remove_non_first_element(list_admin* admin, size_t index)
{
    int result = -1;
    if (index > 0)
    {
        list_head* temp = get_list_head_of_element_n(admin, index-1);
        if ((temp != NULL) && (temp->next != NULL)) // to remove element n, element n-1 and n must exist
        {
            result = 0;
            list_head* to_delete = temp->next;
            temp->next = to_delete->next;
            free(to_delete);
            to_delete = NULL;
            admin->nr_elements--;
        }
    }
    return result;
}
