// SPDX-License-Identifier: Apache-2.0
// Copyright 2025 Doubly Linked List Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "doubly_linked_list.h"
#include <stdint.h>  /* for SIZE_MAX */

/**
 * Create a new node with given data
 * @param data data to store in the node
 * @return pointer to new node, NULL on failure
 */
static struct dll_node *dll_create_node(int data)
{
    struct dll_node *new_node = NULL;
    
    new_node = malloc(sizeof(struct dll_node));
    if (NULL == new_node) {
        return NULL;
    }
    
    new_node->data = data;
    new_node->next = NULL;
    new_node->prev = NULL;
    
    return new_node;
}

struct doubly_linked_list *dll_create(void)
{
    struct doubly_linked_list *list = NULL;
    
    list = malloc(sizeof(struct doubly_linked_list));
    if (NULL == list) {
        return NULL;
    }
    
    list->head = NULL;
    list->tail = NULL;
    list->size = 0;
    
    return list;
}

void dll_destroy(struct doubly_linked_list *list)
{
    if (NULL == list) {
        return;
    }
    
    dll_clear(list);
    free(list);
}

bool dll_insert_front(struct doubly_linked_list *list, int data)
{
    struct dll_node *new_node = NULL;
    
    if (NULL == list) {
        return false;
    }
    
    new_node = dll_create_node(data);
    if (NULL == new_node) {
        return false;
    }
    
    if (NULL == list->head) {
        /* empty list */
        list->head = new_node;
        list->tail = new_node;
    } else {
        /* non-empty list */
        new_node->next = list->head;
        list->head->prev = new_node;
        list->head = new_node;
    }
    
    list->size++;
    return true;
}

bool dll_insert_back(struct doubly_linked_list *list, int data)
{
    struct dll_node *new_node = NULL;
    
    if (NULL == list) {
        return false;
    }
    
    new_node = dll_create_node(data);
    if (NULL == new_node) {
        return false;
    }
    
    if (NULL == list->tail) {
        /* empty list */
        list->head = new_node;
        list->tail = new_node;
    } else {
        /* non-empty list */
        new_node->prev = list->tail;
        list->tail->next = new_node;
        list->tail = new_node;
    }
    
    list->size++;
    return true;
}

bool dll_insert_at(struct doubly_linked_list *list, size_t position, int data)
{
    struct dll_node *new_node = NULL;
    struct dll_node *current = NULL;
    size_t i = 0;
    
    if (NULL == list) {
        return false;
    }
    
    if (position > list->size) {
        return false;
    }
    
    /* special cases for front and back insertion */
    if (0 == position) {
        return dll_insert_front(list, data);
    }
    
    if (position == list->size) {
        return dll_insert_back(list, data);
    }
    
    new_node = dll_create_node(data);
    if (NULL == new_node) {
        return false;
    }
    
    /* traverse to position */
    current = list->head;
    for (i = 0; i < position; i++) {
        current = current->next;
    }
    
    /* insert before current node */
    new_node->next = current;
    new_node->prev = current->prev;
    current->prev->next = new_node;
    current->prev = new_node;
    
    list->size++;
    return true;
}

bool dll_remove_front(struct doubly_linked_list *list)
{
    struct dll_node *node_to_remove = NULL;
    
    if (NULL == list || NULL == list->head) {
        return false;
    }
    
    node_to_remove = list->head;
    
    if (list->head == list->tail) {
        /* only one node */
        list->head = NULL;
        list->tail = NULL;
    } else {
        /* multiple nodes */
        list->head = list->head->next;
        list->head->prev = NULL;
    }
    
    free(node_to_remove);
    list->size--;
    return true;
}

bool dll_remove_back(struct doubly_linked_list *list)
{
    struct dll_node *node_to_remove = NULL;
    
    if (NULL == list || NULL == list->tail) {
        return false;
    }
    
    node_to_remove = list->tail;
    
    if (list->head == list->tail) {
        /* only one node */
        list->head = NULL;
        list->tail = NULL;
    } else {
        /* multiple nodes */
        list->tail = list->tail->prev;
        list->tail->next = NULL;
    }
    
    free(node_to_remove);
    list->size--;
    return true;
}

bool dll_remove_at(struct doubly_linked_list *list, size_t position)
{
    struct dll_node *current = NULL;
    size_t i = 0;
    
    if (NULL == list || position >= list->size) {
        return false;
    }
    
    /* special cases for front and back removal */
    if (0 == position) {
        return dll_remove_front(list);
    }
    
    if (position == (list->size - 1)) {
        return dll_remove_back(list);
    }
    
    /* traverse to position */
    current = list->head;
    for (i = 0; i < position; i++) {
        current = current->next;
    }
    
    /* remove current node */
    current->prev->next = current->next;
    current->next->prev = current->prev;
    
    free(current);
    list->size--;
    return true;
}

bool dll_remove_data(struct doubly_linked_list *list, int data)
{
    size_t position = 0;
    
    if (NULL == list) {
        return false;
    }
    
    position = dll_search(list, data);
    if (SIZE_MAX == position) {
        return false;
    }
    
    return dll_remove_at(list, position);
}

size_t dll_search(struct doubly_linked_list *list, int data)
{
    struct dll_node *current = NULL;
    size_t position = 0;
    
    if (NULL == list) {
        return SIZE_MAX;
    }
    
    current = list->head;
    while (NULL != current) {
        if (data == current->data) {
            return position;
        }
        current = current->next;
        position++;
    }
    
    return SIZE_MAX;
}

bool dll_get_at(struct doubly_linked_list *list, size_t position, int *data)
{
    struct dll_node *current = NULL;
    size_t i = 0;
    
    if (NULL == list || NULL == data || position >= list->size) {
        return false;
    }
    
    current = list->head;
    for (i = 0; i < position; i++) {
        current = current->next;
    }
    
    *data = current->data;
    return true;
}

size_t dll_get_size(struct doubly_linked_list *list)
{
    if (NULL == list) {
        return 0;
    }
    
    return list->size;
}

bool dll_is_empty(struct doubly_linked_list *list)
{
    if (NULL == list) {
        return true;
    }
    
    return (0 == list->size);
}

void dll_print_forward(struct doubly_linked_list *list)
{
    struct dll_node *current = NULL;
    
    if (NULL == list) {
        printf("List is NULL\n");
        return;
    }
    
    if (dll_is_empty(list)) {
        printf("List is empty\n");
        return;
    }
    
    printf("Forward: ");
    current = list->head;
    while (NULL != current) {
        printf("%d", current->data);
        if (NULL != current->next) {
            printf(" <-> ");
        }
        current = current->next;
    }
    printf("\n");
}

void dll_print_backward(struct doubly_linked_list *list)
{
    struct dll_node *current = NULL;
    
    if (NULL == list) {
        printf("List is NULL\n");
        return;
    }
    
    if (dll_is_empty(list)) {
        printf("List is empty\n");
        return;
    }
    
    printf("Backward: ");
    current = list->tail;
    while (NULL != current) {
        printf("%d", current->data);
        if (NULL != current->prev) {
            printf(" <-> ");
        }
        current = current->prev;
    }
    printf("\n");
}

void dll_clear(struct doubly_linked_list *list)
{
    struct dll_node *current = NULL;
    struct dll_node *next_node = NULL;
    
    if (NULL == list) {
        return;
    }
    
    current = list->head;
    while (NULL != current) {
        next_node = current->next;
        free(current);
        current = next_node;
    }
    
    list->head = NULL;
    list->tail = NULL;
    list->size = 0;
}