/* linkedlist.c
 * This file set implements a generic linked list object. It can be used
 * wherever a linke list is required.
 * 
 * NOTE: we do not currently provide a constructor and destructor for the
 * object itself as we assume it will always be part of another strucuture.
 * Having a pointer to it, I think, does not really make sense but costs
 * performance. Consequently, there is is llInit() and llDestroy() and they
 * do what a constructor and destructur do, except for creating the
 * linkedList_t structure itself.
 *
 * File begun on 2007-07-31 by RGerhards
 *
 * Copyright (C) 2007, 2008 by Rainer Gerhards and Adiscon GmbH.
 *
 * This file is part of the rsyslog runtime library.
 *
 * The rsyslog runtime 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 3 of the License, or
 * (at your option) any later version.
 *
 * The rsyslog runtime 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 the rsyslog runtime library.  If not, see <http://www.gnu.org/licenses/>.
 *
 * A copy of the GPL can be found in the file "COPYING" in this distribution.
 * A copy of the LGPL can be found in the file "COPYING.LESSER" in this distribution.
 */
#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

#include "rsyslog.h"
#include "linkedlist.h"


/* Initialize an existing linkedList_t structure
 * pKey destructor may be zero to take care of non-keyed lists.
 */
rsRetVal llInit(linkedList_t *pThis, rsRetVal (*pEltDestructor)(), rsRetVal (*pKeyDestructor)(void*), int (*pCmpOp)())
{
	assert(pThis != NULL);
	assert(pEltDestructor != NULL);

	pThis->pEltDestruct = pEltDestructor;
	pThis->pKeyDestruct = pKeyDestructor;
	pThis->cmpOp = pCmpOp;
	pThis->pKey = NULL;
	pThis->iNumElts = 0;
	pThis->pRoot = NULL;
	pThis->pLast = NULL;

	return RS_RET_OK;
};
	

/* llDestroyEltData - destroys a list element 
 * It is a separate function as the
 * functionality is needed in multiple code-pathes.
 */
static rsRetVal llDestroyElt(linkedList_t *pList, llElt_t *pElt)
{
	DEFiRet;

	assert(pList != NULL);
	assert(pElt != NULL);

	/* we ignore errors during destruction, as we need to try
	 * free the element in any case.
	 */
	if(pElt->pData != NULL)
		pList->pEltDestruct(pElt->pData);
	if(pElt->pKey != NULL)
		pList->pKeyDestruct(pElt->pKey);
	free(pElt);
	pList->iNumElts--; /* one less */

	RETiRet;
}


/* llDestroy - destroys a COMPLETE linkedList
 */
rsRetVal llDestroy(linkedList_t *pThis)
{
	DEFiRet;
	llElt_t *pElt;
	llElt_t *pEltPrev;

	assert(pThis != NULL);

	pElt = pThis->pRoot;
	while(pElt != NULL) {
		pEltPrev = pElt;
		pElt = pElt->pNext;
		/* we ignore errors during destruction, as we need to try
		 * finish the linked list in any case.
		 */
		llDestroyElt(pThis, pEltPrev);
	}
	/* now clean up the pointers */
	pThis->pRoot = NULL;
	pThis->pLast = NULL;

	RETiRet;
}

/* llDestroyRootElt - destroy the root element but otherwise
 * keeps this list intact.  -- rgerhards, 2007-08-03
 */
rsRetVal llDestroyRootElt(linkedList_t *pThis)
{
	DEFiRet;
	llElt_t *pPrev;
	
	if(pThis->pRoot == NULL) {
		ABORT_FINALIZE(RS_RET_EMPTY_LIST);
	}

	pPrev = pThis->pRoot;
	if(pPrev->pNext == NULL) {
		/* it was the only list element */
		pThis->pLast = NULL;
		pThis->pRoot = NULL;
	} else {
		/* there are other list elements */
		pThis->pRoot = pPrev->pNext;
	}

	CHKiRet(llDestroyElt(pThis, pPrev));

finalize_it:
	RETiRet;
}


/* get next user data element of a linked list. The caller must also
 * provide a "cookie" to the function. On initial call, it must be
 * NULL. Other than that, the caller is not allowed to to modify the
 * cookie. In the current implementation, the cookie is an actual
 * pointer to the current list element, but this is nothing that the
 * caller should rely on.
 */
rsRetVal llGetNextElt(linkedList_t *pThis, linkedListCookie_t *ppElt, void **ppUsr)
{
	llElt_t *pElt;
	DEFiRet;

	assert(pThis != NULL);
	assert(ppElt != NULL);
	assert(ppUsr != NULL);

	pElt = *ppElt;

	pElt = (pElt == NULL) ? pThis->pRoot : pElt->pNext;

	if(pElt == NULL) {
		iRet = RS_RET_END_OF_LINKEDLIST;
	} else {
		*ppUsr = pElt->pData;
	}

	*ppElt = pElt;

	RETiRet;
}


/* return the key of an Elt
 * rgerhards, 2007-09-11: note that ppDatea is actually a void**,
 * but I need to make it a void* to avoid lots of compiler warnings.
 * It will be converted later down in the code.
 */
rsRetVal llGetKey(llElt_t *pThis, void *ppData)
{
	assert(pThis != NULL);
	assert(ppData != NULL);

	*(void**) ppData = pThis->pKey;

	return RS_RET_OK;
}


/* construct a new llElt_t
 */
static rsRetVal llEltConstruct(llElt_t **ppThis, void *pKey, void *pData)
{
	DEFiRet;
	llElt_t *pThis;

	assert(ppThis != NULL);

	if((pThis = (llElt_t*) calloc(1, sizeof(llElt_t))) == NULL) {
		ABORT_FINALIZE(RS_RET_OUT_OF_MEMORY);
	}

	pThis->pKey = pKey;
	pThis->pData = pData;

finalize_it:
	*ppThis = pThis;
	RETiRet;
}


/* append a user element to the end of the linked list. This includes setting a key. If no
 * key is desired, simply pass in a NULL pointer for it.
 */
rsRetVal llAppend(linkedList_t *pThis, void *pKey, void *pData)
{
	llElt_t *pElt;
	DEFiRet;
	
	CHKiRet(llEltConstruct(&pElt, pKey, pData));

	pThis->iNumElts++; /* one more */
	if(pThis->pLast == NULL) {
		pThis->pRoot = pElt;
	} else {
		pThis->pLast->pNext = pElt;
	}
	pThis->pLast = pElt;

finalize_it:
	RETiRet;
}


/* unlink a requested element. As we have singly-linked lists, the
 * caller also needs to pass in the previous element (or NULL, if it is the
 * root element).
 * rgerhards, 2007-11-21
 */
static rsRetVal llUnlinkElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev)
{
	assert(pElt != NULL);

	if(pEltPrev == NULL) { /* root element? */
		pThis->pRoot = pElt->pNext;
	} else { /* regular element */
		pEltPrev->pNext = pElt->pNext;
	}

	if(pElt == pThis->pLast)
		pThis->pLast = pEltPrev;

	return RS_RET_OK;
}


/* unlinks and immediately deletes an element. Previous element must
 * be given (or zero if the root element is to be deleted).
 * rgerhards, 2007-11-21
 */
static rsRetVal llUnlinkAndDelteElt(linkedList_t *pThis, llElt_t *pElt, llElt_t *pEltPrev)
{
	DEFiRet;

	assert(pElt != NULL);

	CHKiRet(llUnlinkElt(pThis, pElt, pEltPrev));
	CHKiRet(llDestroyElt(pThis, pElt));

finalize_it:
	RETiRet;
}

/* find a user element based on the provided key - this is the
 * internal variant, which also tracks the last element pointer
 * before the found element. This is necessary to delete elements.
 * NULL means there is no element in front of it, aka the found elt
 * is the root elt.
 * rgerhards, 2007-11-21
 */
static rsRetVal llFindElt(linkedList_t *pThis, void *pKey, llElt_t **ppElt, llElt_t **ppEltPrev)
{
	DEFiRet;
	llElt_t *pElt;
	llElt_t *pEltPrev = NULL;
	int bFound = 0;

	assert(pThis != NULL);
	assert(pKey != NULL);
	assert(ppElt != NULL);
	assert(ppEltPrev != NULL);

	pElt = pThis->pRoot;
	while(pElt != NULL && bFound == 0) {
		if(pThis->cmpOp(pKey, pElt->pKey) == 0)
			bFound = 1;
		else {
			pEltPrev = pElt;
			pElt = pElt->pNext;
		}
	}

	if(bFound == 1) {
		*ppElt = pElt;
		*ppEltPrev = pEltPrev;
	} else
		iRet = RS_RET_NOT_FOUND;

	RETiRet;
}


/* find a user element based on the provided key
 */
rsRetVal llFind(linkedList_t *pThis, void *pKey, void **ppData)
{
	DEFiRet;
	llElt_t *pElt;
	llElt_t *pEltPrev;

	CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev));

	/* if we reach this point, we have found the element */
	*ppData = pElt->pData;

finalize_it:
	RETiRet;
}


/* find a delete an element based on user-provided key. The element is
 * delete, the caller does not receive anything. If we need to receive
 * the element before destruction, we may implement an llFindAndUnlink()
 * at that time.
 * rgerhards, 2007-11-21
 */
rsRetVal llFindAndDelete(linkedList_t *pThis, void *pKey)
{
	DEFiRet;
	llElt_t *pElt;
	llElt_t *pEltPrev;

	CHKiRet(llFindElt(pThis, pKey, &pElt, &pEltPrev));

	/* if we reach this point, we have found an element */
	CHKiRet(llUnlinkAndDelteElt(pThis, pElt, pEltPrev));

finalize_it:
	RETiRet;
}


/* provide the count of linked list elements
 */
rsRetVal llGetNumElts(linkedList_t *pThis, int *piCnt)
{
	DEFiRet;

	assert(pThis != NULL);
	assert(piCnt != NULL);

	*piCnt = pThis->iNumElts;

	RETiRet;
}


/* execute a function on all list members. The functions receives a
 * user-supplied parameter, which may be either a simple value
 * or a pointer to a structure with more data. If the user-supplied
 * function does not return RS_RET_OK, this function here terminates.
 * rgerhards, 2007-08-02
 * rgerhards, 2007-11-21: added functionality to delete a list element.
 * If the called user function returns RS_RET_OK_DELETE_LISTENTRY the current element
 * is deleted.
 */
rsRetVal llExecFunc(linkedList_t *pThis, rsRetVal (*pFunc)(void*, void*), void* pParam)
{
	DEFiRet;
	rsRetVal iRetLL;
	void *pData;
	linkedListCookie_t llCookie = NULL;
	linkedListCookie_t llCookiePrev = NULL; /* previous list element (needed for deletion, NULL = at root) */

	assert(pThis != NULL);
	assert(pFunc != NULL);

	while((iRetLL = llGetNextElt(pThis, &llCookie, (void**)&pData)) == RS_RET_OK) {
		iRet = pFunc(pData, pParam);
		if(iRet == RS_RET_OK_DELETE_LISTENTRY) {
			/* delete element */
			CHKiRet(llUnlinkAndDelteElt(pThis, llCookie, llCookiePrev));
			/* we need to revert back, as we have just deleted the current element.
			 * So the actual current element is the one before it, which happens to be
			 * stored in llCookiePrev. -- rgerhards, 2007-11-21
			 */
			llCookie = llCookiePrev;
		} else if (iRet != RS_RET_OK) {
			FINALIZE;
		}
		llCookiePrev = llCookie;
	}

	if(iRetLL != RS_RET_END_OF_LINKEDLIST)
		iRet = iRetLL;

finalize_it:
	RETiRet;
}

/* vim:set ai:
 */
