/*
	This file is part of Warzone 2100.
	Copyright (C) 1999-2004  Eidos Interactive
	Copyright (C) 2005-2007  Warzone Resurrection Project

	Warzone 2100 is free software; you can redistribute it and/or modify
	it under the terms of the GNU General Public License as published by
	the Free Software Foundation; either version 2 of the License, or
	(at your option) any later version.

	Warzone 2100 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 General Public License for more details.

	You should have received a copy of the GNU General Public License
	along with Warzone 2100; if not, write to the Free Software
	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
*/
/***************************************************************************/
/*
 * Priority queue implementation
 *
 *
 */
/***************************************************************************/

#include "lib/framework/frame.h"

#include "priority.h"
#include "pqueue.h"

/***************************************************************************/

#define QUEUE_INSERTION_TEST	1

/***************************************************************************/

BOOL
queue_Init( QUEUE ** ppQueue, int iMaxElements, int iElementSize,
				QUEUE_CLEAR_FUNC pfClearFunc )
{
	int			i;
	QUEUE_NODE	*psNode;

	/* allocate queue */
	(*ppQueue) = (QUEUE *) MALLOC( sizeof(QUEUE) );

	if ( (*ppQueue) == NULL )
	{
		debug( LOG_ERROR, "queue_Init: couldn't allocate memory for queue" );
		abort();
		return FALSE;
	}

	/* set up free node list */
	(*ppQueue)->psFreeNodeList = NULL;
	for ( i=0; i<iMaxElements; i++ )
	{
		/* allocate node */
		psNode = (QUEUE_NODE *) MALLOC( sizeof(QUEUE_NODE) );

		if ( (*ppQueue) == NULL )
		{
			debug( LOG_ERROR, "queue_Init: couldn't allocate memory for queue node" );
			abort();
			return FALSE;
		}

		/* init node */
		psNode->psElement = NULL;
		psNode->iPriority  = LOW_PRIORITY;

		/* add to head of list */
		psNode->psPrev = NULL;
		psNode->psNext = (*ppQueue)->psFreeNodeList;
		(*ppQueue)->psFreeNodeList = psNode;
	}

	/* init node counts */
	(*ppQueue)->iFreeNodes  = iMaxElements;
	(*ppQueue)->iQueueNodes = 0;

	/* init queue list */
	(*ppQueue)->psNodeQHead = NULL;
	(*ppQueue)->psCurNode   = NULL;

	/* init other queue struct members */
	(*ppQueue)->iElementSize = iElementSize;
	(*ppQueue)->iMaxElements = iMaxElements;
	(*ppQueue)->pfClearFunc  = pfClearFunc;

	return TRUE;
}

/***************************************************************************/

void
queue_Destroy( QUEUE * pQueue )
{
	QUEUE_NODE	*psNode;

	/* free up free node list */
	while ( pQueue->psFreeNodeList != NULL )
	{
		psNode = pQueue->psFreeNodeList;
		pQueue->psFreeNodeList = pQueue->psFreeNodeList->psNext;
		FREE( psNode );
	}

	/* free up node queue */
	while ( pQueue->psNodeQHead != NULL )
	{
		psNode = pQueue->psNodeQHead;
		pQueue->psNodeQHead = pQueue->psNodeQHead->psNext;
		FREE( psNode );
	}
	FREE( pQueue );
}

/***************************************************************************/

void queue_Clear( QUEUE * pQueue )
{
	QUEUE_NODE	*psNode;

	while ( pQueue->psNodeQHead != NULL )
	{
		psNode = pQueue->psNodeQHead;
		pQueue->psNodeQHead = pQueue->psNodeQHead->psNext;

		/* do element callback */
		(pQueue->pfClearFunc)(psNode->psElement);

		/* return node to free node list */
		psNode->psNext = pQueue->psFreeNodeList;
		pQueue->psFreeNodeList = psNode;

		/* update node counts */
		pQueue->iFreeNodes++;
		pQueue->iQueueNodes--;
	}
}

/***************************************************************************/
/*
 * queue_Enqueue
 *
 * Add element to queue in priority order
 */
/***************************************************************************/

void
queue_Enqueue( QUEUE * pQueue, void * psElement, int iPriority )
{
	QUEUE_NODE	*psFreeNode, *psNode, *psNodePrev;

	/* check input */
	ASSERT( PTRVALID(pQueue,sizeof(QUEUE)),
			"queue_Enqueue: queue pointer invalid\n" );
	ASSERT( PTRVALID(psElement,pQueue->iElementSize),
			"queue_Enqueue: element pointer invalid\n" );

#if QUEUE_INSERTION_TEST
	psNode = pQueue->psNodeQHead;
	while ( psNode != NULL )
	{
		ASSERT( psNode->psElement != psElement,
				"duplicate element found\n" );

		psNode = psNode->psNext;
	}
#endif

	/* check list not empty */
	if ( pQueue->psFreeNodeList == NULL )
	{
		debug( LOG_NEVER, "queue_GetFreeElement: all nodes allocated: flushing queue.\n" );
		queue_Clear( pQueue );
	}

	/* get free node from head of list */
	psFreeNode = pQueue->psFreeNodeList;
	pQueue->psFreeNodeList = pQueue->psFreeNodeList->psNext;

	/* update node counts */
	pQueue->iFreeNodes--;
	pQueue->iQueueNodes++;

	/* attach element to node */
	psFreeNode->psElement = psElement;

	/* init pointer to head of queue */
	psNode = pQueue->psNodeQHead;
	psNodePrev = NULL;

	/* init node priority */
	psFreeNode->iPriority = iPriority;

	/* find correct place in queue according to priority */
	while ( psNode != NULL )
	{
		/* break if priority slot found */
		if ( psNode->iPriority < iPriority )
		{
			break;
		}

		/* update pointers */
		psNodePrev = psNode;
		psNode = psNode->psNext;
	}

	/* insert new node */
	psFreeNode->psPrev = psNodePrev;
	psFreeNode->psNext = psNode;

	/* update previous queue node pointer */
	if ( psNodePrev == NULL )
	{
		pQueue->psNodeQHead = psFreeNode;
	}
	else
	{
		psNodePrev->psNext = psFreeNode;
	}

	/* update next queue node pointer */
	if ( psNode != NULL )
	{
		psNode->psPrev = psFreeNode;
	}
}

/***************************************************************************/
/*
 * queue_Dequeue
 *
 * Remove head of queue
 */
/***************************************************************************/

void *
queue_Dequeue( QUEUE * pQueue )
{
	void *			psElement = NULL;
	QUEUE_NODE *	psNode;

	/* check input */
	ASSERT( PTRVALID(pQueue,sizeof(QUEUE)),
			"queue_Dequeue: queue pointer invalid\n" );

	if ( pQueue->psNodeQHead != NULL )
	{
		/* remove node at head of queue */
		psNode = pQueue->psNodeQHead;
		pQueue->psNodeQHead = pQueue->psNodeQHead->psNext;

		/* copy element from node for return */
		psElement = psNode->psElement;

		/* return node to free node list */
		psNode->psNext = pQueue->psFreeNodeList;
		pQueue->psFreeNodeList = psNode;

		/* update node counts */
		pQueue->iFreeNodes++;
		pQueue->iQueueNodes--;

		ASSERT( pQueue->iQueueNodes >= 0, "queue_Dequeue: queue nodes < 0" );
	}

	return psElement;
}

/***************************************************************************/
/*
 * queue_FindElement
 *
 * Find specific element in queue and return node pointer
 */
/***************************************************************************/

QUEUE_NODE *
queue_FindElement( QUEUE * pQueue, void * psElement )
{
	QUEUE_NODE	*psNode, *psNodePrev;

	/* check input */
	ASSERT( PTRVALID(pQueue,sizeof(QUEUE)),
			"queue_FindElement: queue pointer invalid\n" );
	ASSERT( PTRVALID(psElement,pQueue->iElementSize),
			"queue_FindElement: element pointer invalid\n" );

	/* init pointers to head of queue */
	psNodePrev = psNode = pQueue->psNodeQHead;

	/* find node in queue */
	while ( psNodePrev != NULL && psNode != NULL )
	{
		/* return TRUE if match found */
		if ( psNode->psElement == psElement )
		{
			return psNode;
		}

		/* update pointers */
		psNodePrev = psNode;
		psNode = psNode->psNext;
	}

	return NULL;
}

/***************************************************************************/
/*
 * queue_GetHead
 *
 * Get element at head of queue
 */
/***************************************************************************/

void *
queue_GetHead( QUEUE * pQueue )
{
	/* set current node to head of queue */
	pQueue->psCurNode = pQueue->psNodeQHead;

	if ( pQueue->psCurNode != NULL )
	{
		return pQueue->psCurNode->psElement;
	}
	else
	{
		return NULL;
	}
}

/***************************************************************************/
/*
 * queue_GetNext
 *
 * Get next element in queue
 */
/***************************************************************************/

void *
queue_GetNext( QUEUE * pQueue )
{
	/* move to next node */
	if ( pQueue->psCurNode != NULL )
	{
		pQueue->psCurNode = pQueue->psCurNode->psNext;
	}

	/* return node element */
	if ( pQueue->psCurNode == NULL )
	{
		return NULL;
	}
	else
	{
		return pQueue->psCurNode->psElement;
	}
}

/***************************************************************************/
/*
 * queue_RemoveCurrent
 *
 * Remove current node from queue
 */
/***************************************************************************/

BOOL
queue_RemoveCurrent( QUEUE * pQueue )
{
	return queue_RemoveNode( pQueue, pQueue->psCurNode );
}

/***************************************************************************/
/*
 * queue_RemoveNode
 *
 * Remove node from queue
 */
/***************************************************************************/

BOOL
queue_RemoveNode( QUEUE * pQueue, QUEUE_NODE *psNode )
{
	/* check input */
	ASSERT( PTRVALID(pQueue,sizeof(QUEUE)),
			"queue_RemoveNode: queue pointer invalid\n" );
	ASSERT( PTRVALID(psNode,sizeof(QUEUE_NODE)),
			"queue_RemoveNode: node pointer invalid\n" );

	/* if node valid, remove from queue and return to free node list */
	if ( psNode == NULL )
	{
		return FALSE;
	}
	else
	{
		/* remove node from queue */
		if ( psNode->psPrev == NULL && psNode->psNext == NULL )
		{
			pQueue->psCurNode = NULL;
			pQueue->psNodeQHead = NULL;
		}
		else
		{
			/* make previous node point to next */
			if ( psNode->psPrev != NULL )
			{
				psNode->psPrev->psNext = psNode->psNext;
			}

			/* make next node point to previous */
			if ( psNode->psNext != NULL )
			{
				psNode->psNext->psPrev = psNode->psPrev;
			}

			/* set current queue node if necessary */
			if ( pQueue->psCurNode == psNode )
			{
				if ( psNode->psPrev != NULL )
				{
					pQueue->psCurNode = psNode->psPrev;
				}
				else if ( psNode->psNext != NULL )
				{
					pQueue->psCurNode = psNode->psNext;
				}
				else
				{
					pQueue->psCurNode = NULL;
				}
			}

			/* update queue pointer if neccessary */
			if ( pQueue->psNodeQHead == psNode )
			{
				pQueue->psNodeQHead = psNode->psNext;
			}
		}

		/* return node to free node list */
		psNode->psNext = pQueue->psFreeNodeList;
		pQueue->psFreeNodeList = psNode;

		/* update node counts */
		pQueue->iFreeNodes++;
		pQueue->iQueueNodes--;

		ASSERT( pQueue->iQueueNodes >= 0, "queue_RemoveNode: queue nodes < 0" );

		return TRUE;
	}
}

/***************************************************************************/
/*
 * queue_RemoveElement
 *
 * Remove specific element from queue
 */
/***************************************************************************/

BOOL
queue_RemoveElement( QUEUE * pQueue, void * psElement )
{
	QUEUE_NODE *psNode;

	/* check input */
	ASSERT( PTRVALID(pQueue,sizeof(QUEUE)),
			"queue_Dequeue: queue pointer invalid\n" );
	ASSERT( PTRVALID(psElement,pQueue->iElementSize),
			"queue_RemoveElement: element pointer invalid\n" );

	if ( (psNode = queue_FindElement( pQueue, psElement )) != NULL )
	{
		queue_RemoveNode( pQueue, psNode );
		return TRUE;
	}
	else
	{
		return FALSE;
	}
}

/***************************************************************************/
