#include "dll.h"

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

static element_t *create_element(int data, element_t *prev, element_t *next) {
    element_t *elem = (element_t *)malloc(sizeof(*elem));
    if (NULL == elem) {
        return NULL;
    }
    elem->data = data;
    elem->prev = prev;
    elem->next = next;
    return elem;
}

void dll_init(dll *list) {
    list->pos = NULL;
    list->head = NULL;
}

bool dll_is_empty(dll list) {
    return (NULL == list.head && NULL == list.pos);
}

bool dll_value(dll list, int *val) {
    if (dll_is_empty(list)) {
        return false;
    }
    *val = list.pos->data;
    return true;
}

bool dll_is_head(dll list) {
    return (!dll_is_empty(list) && list.pos == list.head);
}

bool dll_is_tail(dll list) {
    return (!dll_is_empty(list) && NULL == list.pos->next);
}

bool dll_is_present(dll list, int data) {
    dll_move_to_head(&list);
    int l_data = 0;
    while (dll_value(list, &l_data)) {
        if (data == l_data) {
            return true;
        } else if (dll_is_tail(list)) {
            return false;
        }
        dll_next(&list);
    }
    return false;
}

static char *robust_realloc(char *str, size_t num) {
    char *new_str = (char *)realloc(str, num * sizeof(*new_str));
    if (NULL == new_str) {
        free(str);
        return NULL;
    }
    return new_str;
}

char *dll_to_str(dll list) {
    char *str = (char *)malloc(sizeof(*str));
    str[0] = 0;
    if (dll_is_empty(list)) {
        return str;
    }
    dll_move_to_head(&list);

    int data = 0;
    while (dll_value(list, &data)) {
        char buffer[12];  // normally largest posible string is -2e9 (11 digits
                          // and space and 0)
        if (dll_is_tail(list)) {
            sprintf(buffer, "%d", data);
            str = robust_realloc(str, strlen(str) + strlen(buffer) + 1);
            if (NULL == str) {
                return NULL;
            }
            strncat(str, buffer, strlen(str) + strlen(buffer) + 1);
            break;
        } else {
            sprintf(buffer, "%d ", data);
            str = robust_realloc(str, strlen(str) + strlen(buffer) + 1);
            if (NULL == str) {
                return NULL;
            }
            strncat(str, buffer, strlen(str) + strlen(buffer) + 1);
        }
        dll_next(&list);
    }
    return str;
}

void dll_move_to_head(dll *list) {
    list->pos = list->head;
}

void dll_next(dll *list) {
    if (!dll_is_tail(*list) && !dll_is_empty(*list)) {
        list->pos = list->pos->next;
    }
}

void dll_prev(dll *list) {
    if (!dll_is_head(*list) && !dll_is_empty(*list)) {
        list->pos = list->pos->prev;
    }
}

bool dll_insert_after(dll *list, int data) {
    if (dll_is_empty(*list)) {
        element_t *elem = create_element(data, NULL, NULL);
        if (NULL == elem) {
            return false;
        }
        list->head = list->pos = elem;
    } else {
        element_t *new_elem = create_element(data, list->pos, list->pos->next);
        if (NULL == new_elem) {
            return false;
        }
        if (!dll_is_tail(*list)) {
            list->pos->next->prev = new_elem;
        }
        list->pos->next = new_elem;
        list->pos = new_elem;
    }
    return true;
}

bool dll_push(dll *list, int data) {
    if (dll_is_empty(*list)) {
        element_t *elem = create_element(data, NULL, NULL);
        if (NULL == elem) {
            return false;
        }
        list->head = list->pos = elem;
    } else {
        element_t *new_elem = create_element(data, NULL, list->head);
        if (NULL == new_elem) {
            return false;
        }
        list->head->prev = new_elem;
        list->head = new_elem;
        list->pos = new_elem;
    }
    return true;
}

bool dll_extract(dll *list, int *val) {
    bool ret = dll_value(*list, val);
    if (!ret) {
        return false;
    }
    element_t *elem = list->pos;
    if (dll_is_tail(*list) && dll_is_head(*list)) {
        list->head = list->pos = NULL;
    } else if (dll_is_tail(*list)) {
        list->pos = list->pos->prev;
        list->pos->next = NULL;
    } else if (dll_is_head(*list)) {
        list->pos = list->pos->next;
        list->pos->prev = NULL;
        list->head = list->pos;
    } else {
        elem->prev->next = list->pos->next;
        elem->next->prev = list->pos->prev;
        list->pos = elem->next;
    }
    free(elem);
    return true;
}
bool dll_pop(dll *list, int *val) {
    dll_move_to_head(list);
    bool ret = dll_value(*list, val);
    if (!ret) {
        return false;
    }
    element_t *elem = list->head;
    if (dll_is_tail(*list) && dll_is_head(*list)) {
        list->head = list->pos = NULL;
    } else {
        list->head->next->prev = NULL;
        list->pos = list->head = list->head->next;
    }
    free(elem);
    return true;
}

void dll_clear(dll *list) {
    dll_move_to_head(list);
    int val = 0;
    while (dll_pop(list, &val)) {
        // we do nothing
    }
}
