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

#include "queue.h"
#include "../array/array.h"
#include "../../common/assert/assert.h"
#include "../../common/misc/misc_utils.h"
#include <string.h>

void _queuePush(WpQueue *queue, void *item, u64 item_size) {
	wpDebugAssert(queue != NULL, "`queue` should not be NULL");
	wpRuntimeAssert(item_size == wpArrayItemSize(queue->items), "Invalid type");

	u64 capacity = wpArrayCapacity(queue->items);
	if (queue->count >= capacity) { return; }

	u64 index = (queue->back)++;
	_arraySet(queue->items, index, item, item_size);
	++(queue->count);

	if (queue->back >= capacity) {
		queue->back = 0;
	}
}

WpQueue *_queuePushAlloc(const WpAllocator *allocator, WpQueue *queue, void *item, u64 item_size) {
	wpDebugAssert(allocator != NULL && queue != NULL && item != NULL,
	                  "`allocator`, `queue` and `item` should not be NULL");
	wpRuntimeAssert(item_size == wpArrayItemSize(queue->items), "Invalid type");

	WpQueue *output = queue;

	u64 capacity = wpArrayCapacity(queue->items);

	// NOTE (Abdelrahman): Extracted into variable to fix MSVC error
	b8 queue_full = queue->count >= capacity;
	if (queue_full) {
		u64 new_capacity = wpMiscUtilsU64RoundUpPow2(capacity * 2);
		u64 array_size   = _arrayCalcAllocSize(new_capacity, item_size);
		u64 alloc_size   = sizeof(WpQueue) + array_size;
		void *buffer     = wpMemAllocatorAlloc(allocator, alloc_size);
		if (!buffer) {
			goto RETURN_QUEUE_PUSH_ALLOC;
		}

		memset((void *)buffer, 0, alloc_size);

		output        = (WpQueue *)buffer;
		output->items = _arrayFromPreallocatedBuffer((void *)(output + 1), array_size, WP_ARRAY_INIT_FILLED, item_size);

		// NOTE (Abdelrahman): When the queue is full, the front and back indices should
		// always be the same
		u64 front_count     = capacity - queue->front;
		u64 back_count      = queue->back;
		void *copy_boundary = (void *)((uptr)(queue->items) + (queue->front * item_size));

		memcpy(output->items, copy_boundary, front_count * item_size);

		/**
		 * NOTE (Abdelrahman): Since this is a ring buffer, the elements at the beginning of the array
		 * aren't always the ones at the front of the queue. When that's the case, the memcpy above
		 * will only copy a subset of the elements. This is why we need to copy the remaining ones.
		 *
		 * Example: Take a queue that looks like this with a capacity of 5 elements
		 *
		 *     0       1   |   2       3       4
		 *  ---------------|-----------------------
		 * |   *   |   *   |   *   |   *   |   *   |
		 *  ---------------|-----------------------
		 *                 |
		 *            queue_front = 2
		 *            queue_back  = 2
		 *
		 * In this case, the first memcpy will only copy elements 2-4. The memcpy below will be
		 * responsible for copying elements 0-1.
		*/
		b8 items_left_to_copy = back_count > 0;
		if (items_left_to_copy) {
			void *back_copy_dst = (void *)((uptr)(output->items) + (front_count * item_size));
			memcpy(back_copy_dst, queue->items, back_count * item_size);
		}

		output->front = 0;
		output->back  = front_count + back_count;
		output->count = queue->count;
	}

	_queuePush(output, item, item_size);

RETURN_QUEUE_PUSH_ALLOC:
	return output;
}

void *_queuePop(WpQueue *queue, u64 item_size) {
	wpDebugAssert(queue != NULL, "`queue` should not be NULL");
	wpRuntimeAssert(item_size == wpArrayItemSize(queue->items), "Invalid type");

	if (queue->count == 0) { return NULL; }

	u64 index = (queue->front)++;
	--(queue->count);

	u64 capacity = wpArrayCapacity(queue->items);
	if (queue->front >= capacity) {
		queue->front = 0;
	}

	return _arrayGet(queue->items, index, item_size);
}
