// BLANKART
//-----------------------------------------------------------------------------
// Copyright (C) 2018-2020 by Sean "Sryder" Ryder
// Copyright (C) 2018-2020 by Kart Krew
//
// This program is free software distributed under the
// terms of the GNU General Public License, version 2.
// See the 'LICENSE' file for more details.
//-----------------------------------------------------------------------------
/// \file  k_bheap.c
/// \brief Binary Heap implementation for SRB2 code base.

#include "k_bheap.h"

#include "z_zone.h"

/*--------------------------------------------------
	static boolean K_BHeapItemValidate(bheap_t *heap, bheapitem_t *item)

		Validates an item on a heap to ensure it is correct and on that heap.

	Input Arguments:-
		heap - The heap to validate the item with
		item - The item to validate

	Return:-
		True if the item is valid, false if it isn't.
--------------------------------------------------*/
static boolean K_BHeapItemValidate(bheap_t *heap, bheapitem_t *item)
{
	boolean heapitemvalid = false;

	I_Assert(heap != NULL);
	I_Assert(item != NULL);

	if ((item->data != NULL) && (item->heapindex < SIZE_MAX / 2) && (item->owner == heap))
	{
		heapitemvalid = true;
	}

	return heapitemvalid;
}

/*--------------------------------------------------
	static bheapitem_t *K_BHeapItemsCompare(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)

		Compares 2 items in the heap to find the better (lower) value one.

	Input Arguments:-
		heap  - The heap to compare items
		item1 - The first item to compare
		item2 - The second item to compare

	Return:-
		The item out of the 2 sent in that has the better value, returns item2 if they are identical
--------------------------------------------------*/
static bheapitem_t *K_BHeapItemsCompare(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)
{
	bheapitem_t *lowervalueitem = NULL;

	I_Assert(heap != NULL);
	I_Assert(K_BHeapValid(heap));
	I_Assert(item1 != NULL);
	I_Assert(item2 != NULL);
	I_Assert(K_BHeapItemValidate(heap, item1));
	I_Assert(K_BHeapItemValidate(heap, item2));

	(void)heap;

	if (item1->value < item2->value)
	{
		lowervalueitem = item1;
	}
	else
	{
		lowervalueitem = item2;
	}

	return lowervalueitem;
}

/*--------------------------------------------------
	static void K_BHeapSwapItems(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)

		Swaps 2 items in the heap

	Input Arguments:-
		heap  - The heap to swap items in
		item1 - The first item to swap in the heap
		item2 - The second item to swap in the heap

	Return:-
		None
--------------------------------------------------*/
static void K_BHeapSwapItems(bheap_t *heap, bheapitem_t *item1, bheapitem_t *item2)
{
	I_Assert(heap != NULL);
	I_Assert(K_BHeapValid(heap));
	I_Assert(item1 != NULL);
	I_Assert(item2 != NULL);
	I_Assert(K_BHeapItemValidate(heap, item1));
	I_Assert(K_BHeapItemValidate(heap, item2));

	(void)heap;

	{
		size_t      tempitemindex = item1->heapindex;
		bheapitem_t tempitemstore = *item1;

		// Swap the items fully with each other
		*item1 = *item2;
		*item2 = tempitemstore;

		// Swap the heap index on each item to be correct
		item2->heapindex = item1->heapindex;
		item1->heapindex = tempitemindex;

		if (item1->indexchanged != NULL)
		{
			item1->indexchanged(item1->data, item1->heapindex);
		}
		if (item2->indexchanged != NULL)
		{
			item2->indexchanged(item2->data, item2->heapindex);
		}
	}
}

/*--------------------------------------------------
	static size_t K_BHeapItemGetParentIndex(bheapitem_t *item)

		Gets the parent index of a heap item

	Input Arguments:-
		item - The item to get the parent index of

	Return:-
		The parent index of the item
--------------------------------------------------*/
static size_t K_BHeapItemGetParentIndex(bheapitem_t *item)
{
	size_t parentindex = SIZE_MAX;

	I_Assert(item != NULL);
	I_Assert(item->heapindex < (SIZE_MAX / 2));

	parentindex = (item->heapindex - 1U) / 2U;

	return parentindex;
}

/*--------------------------------------------------
	static size_t K_BHeapItemGetLeftChildIndex(bheapitem_t *item)

		Gets the left child index of a heap item

	Input Arguments:-
		item - The item to get the left child index of

	Return:-
		The left child index of the item
--------------------------------------------------*/
static size_t K_BHeapItemGetLeftChildIndex(bheapitem_t *item)
{
	size_t leftchildindex = SIZE_MAX;

	I_Assert(item != NULL);
	I_Assert(item->heapindex < (SIZE_MAX / 2));

	leftchildindex = (item->heapindex * 2U) + 1U;

	return leftchildindex;
}

/*--------------------------------------------------
	static size_t K_BHeapItemGetRightChildIndex(bheapitem_t *item)

		Gets the right child index of a heap item

	Input Arguments:-
		item - The item to get the right child index of

	Return:-
		The right child index of the item
--------------------------------------------------*/
static size_t K_BHeapItemGetRightChildIndex(bheapitem_t *item)
{
	size_t rightchildindex = SIZE_MAX;

	I_Assert(item != NULL);
	I_Assert(item->heapindex < (SIZE_MAX / 2));

	rightchildindex = (item->heapindex * 2U) + 2U;

	return rightchildindex;
}

/*--------------------------------------------------
	static void K_BHeapSortUp(bheap_t *heap, bheapitem_t *item)

		Sorts a heapitem up the list to its correct index, lower value items are higher up

	Input Arguments:-
		heap - The heap to sort the item up.
		item - The item to sort up the heap

	Return:-
		None
--------------------------------------------------*/
static void K_BHeapSortUp(bheap_t *heap, bheapitem_t *item)
{
	I_Assert(heap != NULL);
	I_Assert(K_BHeapValid(heap));
	I_Assert(item != NULL);

	if (item->heapindex > 0U)
	{
		size_t parentindex = SIZE_MAX;
		do
		{
			parentindex = K_BHeapItemGetParentIndex(item);

			// Swap the nodes if the parent has a higher value
			if (K_BHeapItemsCompare(heap, item, &heap->array[parentindex]) == item)
			{
				K_BHeapSwapItems(heap, item, &heap->array[parentindex]);
			}
			else
			{
				break;
			}
		} while (parentindex > 0U);
	}
}

/*--------------------------------------------------
	static void K_BHeapSortDown(bheap_t *heap, bheapitem_t *item)

		Sorts a heapitem down the list to its correct index, higher value items are further down

	Input Arguments:-
		heap - The heap to sort the item down.
		item - The item to sort down the heap

	Return:-
		None
--------------------------------------------------*/
static void K_BHeapSortDown(bheap_t *heap, bheapitem_t *item)
{
	I_Assert(heap != NULL);
	I_Assert(K_BHeapValid(heap));
	I_Assert(item != NULL);

	if (heap->count > 0U)
	{
		size_t      leftchildindex  = SIZE_MAX;
		size_t      rightchildindex = SIZE_MAX;
		bheapitem_t *leftchild      = NULL;
		bheapitem_t *rightchild     = NULL;
		bheapitem_t *swapchild      = NULL;
		boolean     noswapneeded    = false;

		do
		{
			leftchildindex  = K_BHeapItemGetLeftChildIndex(item);
			rightchildindex = K_BHeapItemGetRightChildIndex(item);

			if (leftchildindex < heap->count)
			{
				leftchild = &heap->array[leftchildindex];
				swapchild = leftchild;
				if (rightchildindex < heap->count)
				{
					rightchild = &heap->array[rightchildindex];
					// Choose the lower child node to swap with
					if (K_BHeapItemsCompare(heap, leftchild, rightchild) == rightchild)
					{
						swapchild = rightchild;
					}
				}

				// Swap with the lower child, if it's lower than item
				if (K_BHeapItemsCompare(heap, swapchild, item) == swapchild)
				{
					K_BHeapSwapItems(heap, item, swapchild);
				}
				else
				{
					noswapneeded = true;
				}
			}
			else
			{
				noswapneeded = true;
			}

			if (noswapneeded)
			{
				break;
			}

		} while (item->heapindex < (heap->count - 1U));
	}
}

/*--------------------------------------------------
	boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity)

		See header file for description.
--------------------------------------------------*/
boolean K_BHeapInit(bheap_t *const heap, size_t initialcapacity)
{
	boolean initsuccess = false;

	if (heap == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapInit.\n");
	}
	else if (initialcapacity == 0U)
	{
		CONS_Debug(DBG_GAMELOGIC, "initialcapacity is 0 in K_BHeapInit.\n");
	}
	else
	{
		heap->array = Z_Calloc(initialcapacity * sizeof(bheapitem_t), PU_STATIC, NULL);

		if (heap->array == NULL)
		{
			I_Error("K_BHeapInit: Out of Memory.");
		}

		heap->capacity = initialcapacity;
		heap->count    = 0U;

		initsuccess = true;
	}

	return initsuccess;
}

/*--------------------------------------------------
	boolean K_BHeapValid(bheap_t *const heap)

		See header file for description.
--------------------------------------------------*/
boolean K_BHeapValid(bheap_t *const heap)
{
	boolean heapvalid = false;

	if (heap == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapValid.\n");
	}
	else
	{
		if ((heap->capacity > 0U) && (heap->array != NULL))
		{
			heapvalid = true;
		}
	}

	return heapvalid;
}

/*--------------------------------------------------
	boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback)

		See header file for description.
--------------------------------------------------*/
boolean K_BHeapPush(bheap_t *const heap, void *const item, UINT32 value, updateindexfunc changeindexcallback)
{
	boolean pushsuccess = false;
	if (heap == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapPush.\n");
	}
	else if (!K_BHeapValid(heap))
	{
		CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapPush.\n");
	}
	else if (item == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL item in K_BHeapPush.\n");
	}
	else if (heap->count >= (SIZE_MAX / 2))
	{
		CONS_Debug(DBG_GAMELOGIC, "Tried to push too many items on binary heap in K_BHeapPush.\n");
	}
	else
	{
		bheapitem_t *newitem = NULL;
		// If the capacity of the heap has been reached, a realloc is needed
		// I'm just doing a basic double of capacity for simplicity
		if (heap->count >= heap->capacity)
		{
			size_t newarraycapacity = heap->capacity * 2;
			heap->array = Z_Realloc(heap->array, newarraycapacity * sizeof(bheapitem_t), PU_STATIC, NULL);

			if (heap->array == NULL)
			{
				I_Error("K_BHeapPush: Out of Memory.");
			}

			heap->capacity = newarraycapacity;
		}

		newitem = &heap->array[heap->count];

		newitem->heapindex    = heap->count;
		newitem->owner        = heap;
		newitem->data         = item;
		newitem->value        = value;
		newitem->indexchanged = changeindexcallback;

		if (newitem->indexchanged != NULL)
		{
			newitem->indexchanged(newitem->data, newitem->heapindex);
		}

		heap->count++;

		K_BHeapSortUp(heap, &heap->array[heap->count - 1U]);

		pushsuccess = true;
	}

	return pushsuccess;
}

/*--------------------------------------------------
	boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage)

		See header file for description.
--------------------------------------------------*/
boolean K_BHeapPop(bheap_t *const heap, bheapitem_t *const returnitemstorage)
{
	boolean popsuccess = false;
	if (heap == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapPop.\n");
	}
	else if (!K_BHeapValid(heap))
	{
		CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapPop.\n");
	}
	else if (heap->count == 0U)
	{
		CONS_Debug(DBG_GAMELOGIC, "Tried to Pop from empty heap in K_BHeapPop.\n");
	}
	else if (returnitemstorage == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL returnitemstorage in K_BHeapPop.\n");
	}
	else
	{
		*returnitemstorage = heap->array[0];

		// Invalidate the heap related data from the return item
		returnitemstorage->owner = NULL;
		returnitemstorage->heapindex = SIZE_MAX;

		if (returnitemstorage->indexchanged != NULL)
		{
			returnitemstorage->indexchanged(returnitemstorage->data, returnitemstorage->heapindex);
		}

		heap->count--;

		heap->array[0] = heap->array[heap->count];
		heap->array[0].heapindex = 0U;
		memset(&heap->array[heap->count], 0x00, sizeof(bheapitem_t));
		if (heap->array[0].indexchanged != NULL)
		{
			heap->array[0].indexchanged(heap->array[0].data, heap->array[0].heapindex);
		}

		K_BHeapSortDown(heap, &heap->array[0]);
		popsuccess = true;
	}

	return popsuccess;
}

/*--------------------------------------------------
	boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue)

		See header file for description.
--------------------------------------------------*/
boolean K_UpdateBHeapItemValue(bheapitem_t *const item, const UINT32 newvalue)
{
	boolean updatevaluesuccess = false;
	if (item == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL item in K_UpdateHeapItemValue.\n");
	}
	else if (item->owner == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "item has NULL owner in K_UpdateHeapItemValue.\n");
	}
	else if (K_BHeapItemValidate(item->owner, item) == false)
	{
		CONS_Debug(DBG_GAMELOGIC, "Invalid item in K_UpdateHeapItemValue.\n");
	}
	else if (K_BHeapValid(item->owner) == false)
	{
		CONS_Debug(DBG_GAMELOGIC, "Invalid item owner in K_UpdateHeapItemValue.\n");
	}
	else
	{
		size_t oldvalue = item->value;
		item->value = newvalue;
		if (newvalue < oldvalue)
		{
			K_BHeapSortUp(item->owner, item);
		}
		else if (newvalue > oldvalue)
		{
			K_BHeapSortDown(item->owner, item);
		}
		else
		{
			// No change is needed as the value is the same
		}

	}

	return updatevaluesuccess;
}

/*--------------------------------------------------
	size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index)

		See header file for description.
--------------------------------------------------*/
size_t K_BHeapContains(bheap_t *const heap, void *const data, size_t index)
{
	size_t heapindexwithdata = SIZE_MAX;

	if (heap == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapContains.\n");
	}
	else if (!K_BHeapValid(heap))
	{
		CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapContains.\n");
	}
	else if (data == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL data in K_BHeapContains.\n");
	}
	else
	{
		if ((heap->count != 0U) && (index < heap->count))
		{
			if (heap->array[index].data == data)
			{
				heapindexwithdata = index;
			}
		}
		else if (index == SIZE_MAX)
		{
			size_t i;
			for (i = 0; i < heap->count; i++)
			{
				if (heap->array[i].data == data)
				{
					heapindexwithdata = i;
					break;
				}
			}
		}
	}

	return heapindexwithdata;
}

boolean K_BHeapFree(bheap_t *const heap)
{
	boolean freesuccess = false;

	if (heap == NULL)
	{
		CONS_Debug(DBG_GAMELOGIC, "NULL heap in K_BHeapFree.\n");
	}
	else if (!K_BHeapValid(heap))
	{
		CONS_Debug(DBG_GAMELOGIC, "Uninitialised heap in K_BHeapFree.\n");
	}
	else
	{
		Z_Free(heap->array);
		heap->array    = NULL;
		heap->capacity = 0U;
		heap->count    = 0U;
		freesuccess    = true;
	}

	return freesuccess;
}
