// vim:fileencoding=utf-8:foldmethod=marker

#include "./dbl_list.h"
#include "../mem/allocator/mem_allocator.h"
#include "../../common/assert/assert.h"
#include "../../common/aliases/aliases.h"
#include "../../common/platform/platform.h"
#include <stddef.h>

wp_intern WpDblList _node_to_list(WpDblNode *node, u64 item_size);
wp_intern inline void _dblListValidate(const WpDblList *list, u64 item_size);
wp_intern inline void _dblListNodeValidate(const WpDblList *list, const WpDblNode *node, u64 item_size);

WpDblList *_dblListAlloc(const WpAllocator *allocator, u64 item_size) {
	wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");

	WpDblList *list = wpMemAllocatorAlloc(allocator, sizeof(WpDblList));
	if (!list) { goto DBL_LIST_ALLOC_RETURN; }

	memset((void *)list, 0, sizeof(WpDblList));
	list->magic     = WP_DBL_LIST_MAGIC;
	list->item_size = item_size;

DBL_LIST_ALLOC_RETURN:
	return list;
}

WpDblNode *_dblListNodeAlloc(const WpAllocator *allocator, void *item, u64 item_size) {
	wpDebugAssert(allocator != NULL, "`allocator` should not be NULL");

	WpDblNode *node = wpMemAllocatorAlloc(allocator, sizeof(WpDblNode));
	if (!node) { goto DBL_LIST_NODE_ALLOC_RETURN; }

	memset((void *)node, 0, sizeof(WpDblNode));
	node->item             = item;
	node->header.magic     = WP_DBL_NODE_MAGIC;
	node->header.item_size = item_size;

DBL_LIST_NODE_ALLOC_RETURN:
	return node;
}

WpDblNode *_dblListGet(const WpDblList *list, u64 index, u64 item_size) {
	wpDebugAssert(list != NULL, "`list` should not be NULL");
	_dblListValidate(list, item_size);
	wpRuntimeAssert(index < list->node_count, "`index` is out of bounds");

	WpDblNode *output  = NULL;
	WpDblNode *current = list->first;
	for (u64 i = 1; i <= index; ++i) {
		current = current->header.next;
	}

	output = current;

	return output;
}

void _dblListPushFront(WpDblList *list, WpDblNode *node, u64 item_size) {
	wpDebugAssert(list != NULL && node != NULL && (node->item) != NULL, "`list`, `node` and `node->item` should not be NULL");
	_dblListValidate(list, item_size);
	_dblListNodeValidate(list, node, item_size);

	WpDblList node_list = _node_to_list(node, item_size);

	if (list->node_count == 0) {
		*list = node_list;
		return;
	}

	list->node_count += node_list.node_count;

	WpDblNode *first = list->first;
	if (first) {
		first->header.prev = node_list.last;
	}

	list->first          = node_list.first;
	node_list.last->header.next = first;
}

void _dblListPushBack(WpDblList *list, WpDblNode *node, u64 item_size) {
	wpDebugAssert(list != NULL && node != NULL && (node->item) != NULL, "`list`, `node` and `node->item` should not be NULL");
	_dblListValidate(list, item_size);
	_dblListNodeValidate(list, node, item_size);

	WpDblList node_list = _node_to_list(node, item_size);

	if (list->node_count == 0) {
		*list = node_list;
		return;
	}

	list->node_count += node_list.node_count;

	WpDblNode *last = list->last;
	if (last) {
		last->header.next = node_list.first;
	}

	list->last            = node_list.last;
	node_list.first->header.prev = last;
}

void _dblListInsert(WpDblList *list, WpDblNode *node, u64 index, u64 item_size) {
	wpDebugAssert(list != NULL && node != NULL && (node->item) != NULL, "`list`, `node` and `node->item` should not be NULL");
	_dblListValidate(list, item_size);
	_dblListNodeValidate(list, node, item_size);

	if (index == 0) {
		_dblListPushFront(list, node, item_size);
		return;
	} else if (index == list->node_count) {
		_dblListPushBack(list, node, item_size);
		return;
	}

	WpDblNode *dst_node = _dblListGet(list, index, item_size);
	if (!dst_node) {
		return;
	}

	WpDblList node_list = _node_to_list(node, item_size);

	list->node_count += node_list.node_count;

	WpDblNode *prev = dst_node->header.prev;

	dst_node->header.prev = node_list.last;
	prev->header.next     = node_list.first;

	node_list.first->header.prev = prev;
	node_list.last->header.next  = dst_node;
}

WpDblNode *_dblListPopFront(WpDblList *list, u64 item_size) {
	wpDebugAssert(list != NULL, "`list` should not be NULL");
	_dblListValidate(list, item_size);

	WpDblNode *output = NULL;

	if (list->node_count == 0) {
		goto RETURN_LIST_POP_FRONT;
	}

	output = list->first;

	if (list->node_count == 1) {
		*list = (WpDblList){.magic = WP_DBL_LIST_MAGIC, .item_size = item_size};
		goto RETURN_LIST_POP_FRONT;
	}

	--(list->node_count);
	list->first = output->header.next;

	output->header.prev = output->header.next = NULL;

RETURN_LIST_POP_FRONT:
	return output;
}

WpDblNode *_dblListPopBack(WpDblList *list, u64 item_size) {
	wpDebugAssert(list != NULL, "`list` should not be NULL");
	_dblListValidate(list, item_size);

	WpDblNode *output = NULL;

	if (list->node_count == 0) {
		goto RETURN_LIST_POP_BACK;
	}

	output = list->last;

	if (list->node_count == 1) {
		*list = (WpDblList){.magic = WP_DBL_LIST_MAGIC, .item_size = item_size};
		goto RETURN_LIST_POP_BACK;
	}

	--(list->node_count);
	list->last = output->header.prev;

	output->header.prev = output->header.next = NULL;

RETURN_LIST_POP_BACK:
	return output;
}

WpDblNode *_dblListRemove(WpDblList *list, u64 index, u64 item_size) {
	wpDebugAssert(list != NULL, "`list` should not be NULL");
	_dblListValidate(list, item_size);

	WpDblNode *output = NULL;

	if (index == 0) {
		output = _dblListPopFront(list, item_size);
		goto RETURN_LIST_REMOVE;
	} else if (index == list->node_count) {
		output = _dblListPopBack(list, item_size);
		goto RETURN_LIST_REMOVE;
	}

	output = _dblListGet(list, index, item_size);
	if (!output) {
		goto RETURN_LIST_REMOVE;
	}

	output->header.prev->header.next = output->header.next;
	output->header.next->header.prev = output->header.prev;

	--(list->node_count);

	output->header.prev = output->header.next = NULL;

RETURN_LIST_REMOVE:
	return output;
}

void _dblListEmpty(WpDblList *list, u64 item_size) {
	wpDebugAssert(list != NULL, "`list` should not be NULL");
	_dblListValidate(list, item_size);

	u64 count = list->node_count;
	for (u64 i = 0; i < count; ++i) {
		_dblListPopBack(list, item_size);
	}
}

wp_intern WpDblList _node_to_list(WpDblNode *node, u64 item_size) {
	WpDblList output = {
		.magic      = WP_DBL_LIST_MAGIC,
		.first      = node,
		.last       = node,
		.node_count = 1,
		.item_size  = item_size,
	};

	while (output.first->header.prev != NULL) {
		output.first = output.first->header.prev;
		++(output.node_count);
	}

	while (output.last->header.next != NULL) {
		output.last = output.last->header.next;
		++(output.node_count);
	}

	return output;
}

wp_intern inline void _dblListValidate(const WpDblList *list, u64 item_size) {
	wpRuntimeAssert(list->magic == WP_DBL_LIST_MAGIC, "`list` isn't a valid wp list type");
	wpRuntimeAssert(list->item_size == item_size, "Invalid item provided");
}

wp_intern inline void _dblListNodeValidate(const WpDblList *list, const WpDblNode *node, u64 item_size) {
	wpRuntimeAssert(node->header.magic == WP_DBL_NODE_MAGIC, "`node` isn't a valid wp node type");
	wpRuntimeAssert(list->item_size == node->header.item_size, "Mismatched `list` and `node` types");
	wpRuntimeAssert(node->header.item_size == item_size, "Invalid item provided");
}
