/*
 * This file is part of the PCEPlib, a PCEP protocol library.
 *
 * Copyright (C) 2020 Volta Networks https://voltanet.io/
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <https://www.gnu.org/licenses/>.
 *
 * Author : Brady Johnson <brady@voltanet.io>
 *
 */


#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

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

#include "pcep_utils_logging.h"
#include "pcep_utils_memory.h"
#include "pcep_utils_ordered_list.h"

/* Compare function that simply compares pointers.
 * return:
 *   < 0  if new_entry  < list_entry
 *   == 0 if new_entry == list_entry (new_entry will be inserted after
 * list_entry) > 0  if new_entry  > list_entry
 */
int pointer_compare_function(void *list_entry, void *new_entry)
{
	return (char *)new_entry - (char *)list_entry;
}

ordered_list_handle *ordered_list_initialize(ordered_compare_function func_ptr)
{
	ordered_list_handle *handle =
		pceplib_malloc(PCEPLIB_INFRA, sizeof(ordered_list_handle));
	memset(handle, 0, sizeof(ordered_list_handle));
	handle->head = NULL;
	handle->num_entries = 0;
	handle->compare_function = func_ptr;

	return handle;
}


/* free all the ordered_list_node resources and the ordered_list_handle.
 * it is assumed that the user is responsible fore freeing the data
 * pointed to by the nodes.
 */
void ordered_list_destroy(ordered_list_handle *handle)
{
	if (handle == NULL) {
		return;
	}

	ordered_list_node *node = handle->head;
	ordered_list_node *next;

	while (node != NULL) {
		next = node->next_node;
		pceplib_free(PCEPLIB_INFRA, node);
		node = next;
	}

	pceplib_free(PCEPLIB_INFRA, handle);
}


ordered_list_node *ordered_list_add_node(ordered_list_handle *handle,
					 void *data)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_add_node, the list has not been initialized",
			__func__);
		return NULL;
	}
	handle->num_entries++;

	ordered_list_node *new_node =
		pceplib_malloc(PCEPLIB_INFRA, sizeof(ordered_list_node));
	memset(new_node, 0, sizeof(ordered_list_node));
	new_node->data = data;
	new_node->next_node = NULL;

	/* check if its an empty list */
	if (handle->head == NULL) {
		handle->head = new_node;

		return new_node;
	}

	ordered_list_node *prev_node = handle->head;
	ordered_list_node *node = prev_node;
	int compare_result;

	while (node != NULL) {
		compare_result = handle->compare_function(node->data, data);
		if (compare_result < 0) {
			/* insert the node */
			new_node->next_node = node;
			if (handle->head == node) {
				/* add it at the beginning of the list */
				handle->head = new_node;
			} else {
				prev_node->next_node = new_node;
			}

			return new_node;
		}

		/* keep searching with the next node in the list */
		prev_node = node;
		node = node->next_node;
	}

	/* at the end of the list, add it here */
	prev_node->next_node = new_node;

	return new_node;
}


ordered_list_node *ordered_list_find2(ordered_list_handle *handle, void *data,
				      ordered_compare_function compare_func)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_find2, the list has not been initialized",
			__func__);
		return NULL;
	}

	ordered_list_node *node = handle->head;
	int compare_result;

	while (node != NULL) {
		compare_result = compare_func(node->data, data);
		if (compare_result == 0) {
			return node;
		} else {
			node = node->next_node;
		}
	}

	return NULL;
}


ordered_list_node *ordered_list_find(ordered_list_handle *handle, void *data)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_find, the list has not been initialized",
			__func__);
		return NULL;
	}

	return ordered_list_find2(handle, data, handle->compare_function);
}


void *ordered_list_remove_first_node(ordered_list_handle *handle)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_remove_first_node, the list has not been initialized",
			__func__);
		return NULL;
	}

	if (handle->head == NULL) {
		return NULL;
	}
	handle->num_entries--;

	void *data = handle->head->data;
	ordered_list_node *next_node = handle->head->next_node;
	pceplib_free(PCEPLIB_INFRA, handle->head);
	handle->head = next_node;

	return data;
}


void *
ordered_list_remove_first_node_equals2(ordered_list_handle *handle, void *data,
				       ordered_compare_function compare_func)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_remove_first_node_equals2, the list has not been initialized",
			__func__);
		return NULL;
	}

	if (handle->head == NULL) {
		return NULL;
	}

	ordered_list_node *prev_node = handle->head;
	ordered_list_node *node = prev_node;
	bool keep_walking = true;
	void *return_data = NULL;
	int compare_result;

	while (node != NULL && keep_walking) {
		compare_result = compare_func(node->data, data);
		if (compare_result == 0) {
			return_data = node->data;
			keep_walking = false;
			handle->num_entries--;

			/* adjust the corresponding pointers accordingly */
			if (handle->head == node) {
				/* its the first node in the list */
				handle->head = node->next_node;
			} else {
				prev_node->next_node = node->next_node;
			}

			pceplib_free(PCEPLIB_INFRA, node);
		} else {
			prev_node = node;
			node = node->next_node;
		}
	}

	return return_data;
}


void *ordered_list_remove_first_node_equals(ordered_list_handle *handle,
					    void *data)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_remove_first_node_equals, the list has not been initialized",
			__func__);
		return NULL;
	}

	return ordered_list_remove_first_node_equals2(handle, data,
						      handle->compare_function);
}


void *ordered_list_remove_node(ordered_list_handle *handle,
			       ordered_list_node *prev_node,
			       ordered_list_node *node_toRemove)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_remove_node, the list has not been initialized",
			__func__);
		return NULL;
	}

	if (handle->head == NULL) {
		return NULL;
	}

	void *return_data = node_toRemove->data;
	handle->num_entries--;

	if (node_toRemove == handle->head) {
		handle->head = node_toRemove->next_node;
	} else {
		prev_node->next_node = node_toRemove->next_node;
	}

	pceplib_free(PCEPLIB_INFRA, node_toRemove);

	return return_data;
}

void *ordered_list_remove_node2(ordered_list_handle *handle,
				ordered_list_node *node_to_remove)
{
	if (handle == NULL) {
		pcep_log(
			LOG_WARNING,
			"%s: ordered_list_remove_node2, the list has not been initialized",
			__func__);
		return NULL;
	}

	if (handle->head == NULL) {
		return NULL;
	}

	ordered_list_node *node = handle->head;
	ordered_list_node *prev_node = handle->head;

	while (node != NULL) {
		if (node == node_to_remove) {
			return (ordered_list_remove_node(handle, prev_node,
							 node));
		} else {
			prev_node = node;
			node = node->next_node;
		}
	}

	return NULL;
}
