#include <malloc.h>
#include <stdlib.h>

#include "linkedlist.h"

void linked_list_init(linked_list* list) {
    list->first = NULL;
    list->last = NULL;
    list->size = 0;
}

void linked_list_destroy(linked_list* list) {
    linked_list_clear(list);
}

unsigned int linked_list_size(linked_list* list) {
    return list->size;
}

void linked_list_clear(linked_list* list) {
    linked_list_node* node = list->first;
    while(node != NULL) {
        linked_list_node* next = node->next;
        free(node);
        node = next;
    }

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

bool linked_list_contains(linked_list* list, void* value) {
    return linked_list_index_of(list, value) != -1;
}

int linked_list_index_of(linked_list* list, void* value) {
    int i = 0;
    linked_list_node* node = list->first;
    while(node != NULL) {
        if(node->value == value) {
            return i;
        }

        i++;
        node = node->next;
    }

    return -1;
}

static linked_list_node* linked_list_get_node(linked_list* list, unsigned int index) {
    if(index < 0 || index >= list->size) {
        return NULL;
    }

    linked_list_node* node = NULL;

    if(index > (list->size - 1) / 2) {
        node = list->last;
        unsigned int pos = list->size - 1;
        while(node != NULL && pos != index) {
            node = node->prev;
            pos--;
        }
    } else {
        node = list->first;
        unsigned int pos = 0;
        while(node != NULL && pos != index) {
            node = node->next;
            pos++;
        }
    }

    return node;
}

void* linked_list_get(linked_list* list, unsigned int index) {
    linked_list_node* node = linked_list_get_node(list, index);
    return node != NULL ? node->value : NULL;
}

bool linked_list_add(linked_list* list, void* value) {
    linked_list_node* node = (linked_list_node*) calloc(1, sizeof(linked_list_node));
    if(node == NULL) {
        return false;
    }

    node->value = value;
    node->next = NULL;

    if(list->first == NULL || list->last == NULL) {
        node->prev = NULL;

        list->first = node;
        list->last = node;
    } else {
        node->prev = list->last;

        list->last->next = node;
        list->last = node;
    }

    list->size++;
    return true;
}

bool linked_list_add_at(linked_list* list, unsigned int index, void* value) {
    linked_list_node* node = (linked_list_node*) calloc(1, sizeof(linked_list_node));
    if(node == NULL) {
        return false;
    }

    node->value = value;

    if(index == 0) {
        node->prev = NULL;
        node->next = list->first;

        list->first = node;
    } else {
        linked_list_node* prev = linked_list_get_node(list, index - 1);
        if(prev == NULL) {
            free(node);
            return false;
        }

        node->prev = prev;
        node->next = prev->next;

        prev->next = node;
    }

    if(node->next != NULL) {
        node->next->prev = node;
    } else {
        list->last = node;
    }

    list->size++;
    return true;
}

void linked_list_add_sorted(linked_list* list, void* value, void* userData, int (*compare)(void* userData, const void* p1, const void* p2)) {
    if(compare != NULL) {
        unsigned int i = 0;
        linked_list_node* node = list->first;
        while(node != NULL) {
            if(compare(userData, value, node->value) < 0) {
                linked_list_add_at(list, i, value);
                return;
            }

            i++;
            node = node->next;
        }
    }

    linked_list_add(list, value);
}

static void linked_list_remove_node(linked_list* list, linked_list_node* node) {
    if(node->prev != NULL) {
        node->prev->next = node->next;
    }

    if(node->next != NULL) {
        node->next->prev = node->prev;
    }

    if(list->first == node) {
        list->first = node->next;
    }

    if(list->last == node) {
        list->last = node->prev;
    }

    list->size--;

    free(node);
}

bool linked_list_remove(linked_list* list, void* value) {
    bool found = false;

    linked_list_node* node = list->first;
    while(node != NULL) {
        linked_list_node* next = node->next;

        if(node->value == value) {
            found = true;

            linked_list_remove_node(list, node);
        }

        node = next;
    }

    return found;
}

bool linked_list_remove_at(linked_list* list, unsigned int index) {
    linked_list_node* node = linked_list_get_node(list, index);
    if(node == NULL) {
        return false;
    }

    linked_list_remove_node(list, node);
    return true;
}

void linked_list_sort(linked_list* list, void* userData, int (*compare)(void* userData, const void* p1, const void* p2)) {
    bool swapped = true;
    while(swapped) {
        swapped = false;

        linked_list_node* curr = list->first;
        if(curr == NULL) {
            return;
        }

        linked_list_node* next = NULL;
        while((next = curr->next) != NULL) {
            if(compare(userData, curr->value, next->value) > 0) {
                void* temp = curr->value;
                curr->value = next->value;
                next->value = temp;

                swapped = true;
            }

            curr = next;
        }
    }
}

void linked_list_iterate(linked_list* list, linked_list_iter* iter) {
    iter->list = list;
    linked_list_iter_restart(iter);
}

void linked_list_iter_restart(linked_list_iter* iter) {
    if(iter->list == NULL) {
        return;
    }

    iter->curr = NULL;
    iter->next = iter->list->first;
}

bool linked_list_iter_has_next(linked_list_iter* iter) {
    return iter->next != NULL && iter->next->value != NULL;
}

void* linked_list_iter_next(linked_list_iter* iter) {
    linked_list_node* next = iter->next;
    if(next == NULL) {
        return NULL;
    }

    void* value = next->value;
    if(value == NULL) {
        return NULL;
    }

    iter->curr = next;
    iter->next = next->next;
    return value;
}

void linked_list_iter_remove(linked_list_iter* iter) {
    if(iter->curr == NULL) {
        return;
    }

    linked_list_remove_node(iter->list, iter->curr);
    iter->curr = NULL;
}