/* This Source Code Form is subject to the terms of the Mozilla Public
 * License, v. 2.0. If a copy of the MPL was not distributed with this
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */

/*
 * list.c
 *
 * This contains the implementation of NSS's thread-safe linked list.
 */

#ifndef BASE_H
#include "base.h"
#endif /* BASE_H */

struct nssListElementStr {
    PRCList  link;
    void    *data;
};

typedef struct nssListElementStr nssListElement;

struct nssListStr {
    NSSArena       *arena;
    PZLock         *lock;
    nssListElement *head;
    PRUint32        count;
    nssListCompareFunc compareFunc;
    nssListSortFunc    sortFunc;
    PRBool i_alloced_arena;
};

struct nssListIteratorStr {
    PZLock *lock;
    nssList *list;
    nssListElement *current;
};

#define NSSLIST_LOCK_IF(list) \
    if ((list)->lock) PZ_Lock((list)->lock)

#define NSSLIST_UNLOCK_IF(list) \
    if ((list)->lock) PZ_Unlock((list)->lock)

static PRBool
pointer_compare(void *a, void *b)
{
    return (PRBool)(a == b);
}

static nssListElement *
nsslist_get_matching_element(nssList *list, void *data)
{
    PRCList *link;
    nssListElement *node;
    node = list->head;
    if (!node) {
	return NULL;
    }
    link = &node->link;
    while (node) {
	/* using a callback slows things down when it's just compare ... */
	if (list->compareFunc(node->data, data)) {
	    break;
	}
	link = &node->link;
	if (link == PR_LIST_TAIL(&list->head->link)) {
	    node = NULL;
	    break;
	}
	node = (nssListElement *)PR_NEXT_LINK(&node->link);
    }
    return node;
}

NSS_IMPLEMENT nssList *
nssList_Create
(
  NSSArena *arenaOpt,
  PRBool threadSafe
)
{
    NSSArena *arena;
    nssList *list;
    PRBool i_alloced;
    if (arenaOpt) {
	arena = arenaOpt;
	i_alloced = PR_FALSE;
    } else {
	arena = nssArena_Create();
	i_alloced = PR_TRUE;
    }
    if (!arena) {
	return (nssList *)NULL;
    }
    list = nss_ZNEW(arena, nssList);
    if (!list) {
	if (!arenaOpt) {
	    NSSArena_Destroy(arena);
	}
	return (nssList *)NULL;
    }
    if (threadSafe) {
	list->lock = PZ_NewLock(nssILockOther);
	if (!list->lock) {
	    if (arenaOpt) {
		nss_ZFreeIf(list);
	    } else {
		NSSArena_Destroy(arena);
	    }
	    return (nssList *)NULL;
	}
    }
    list->arena = arena;
    list->i_alloced_arena = i_alloced;
    list->compareFunc = pointer_compare;
    return list;
}

NSS_IMPLEMENT PRStatus
nssList_Destroy(nssList *list)
{
    if (!list->i_alloced_arena) {
	nssList_Clear(list, NULL);
    }
    if (list->lock) {
	(void)PZ_DestroyLock(list->lock);
    }
    if (list->i_alloced_arena) {
	NSSArena_Destroy(list->arena);
	list = NULL;
    }
    nss_ZFreeIf(list);
    return PR_SUCCESS;
}

NSS_IMPLEMENT void
nssList_SetCompareFunction(nssList *list, nssListCompareFunc compareFunc)
{
    list->compareFunc = compareFunc;
}

NSS_IMPLEMENT void
nssList_SetSortFunction(nssList *list, nssListSortFunc sortFunc)
{
    /* XXX if list already has elements, sort them */
    list->sortFunc = sortFunc;
}

NSS_IMPLEMENT nssListCompareFunc
nssList_GetCompareFunction(nssList *list)
{
    return list->compareFunc;
}

NSS_IMPLEMENT void
nssList_Clear(nssList *list, nssListElementDestructorFunc destructor)
{
    PRCList *link;
    nssListElement *node, *tmp;
    NSSLIST_LOCK_IF(list);
    node = list->head;
    list->head = NULL;
    while (node && list->count > 0) {
	if (destructor) (*destructor)(node->data);
	link = &node->link;
	tmp = (nssListElement *)PR_NEXT_LINK(link);
	PR_REMOVE_LINK(link);
	nss_ZFreeIf(node);
	node = tmp;
	--list->count;
    }
    NSSLIST_UNLOCK_IF(list);
}

static PRStatus
nsslist_add_element(nssList *list, void *data)
{
    nssListElement *node = nss_ZNEW(list->arena, nssListElement);
    if (!node) {
	return PR_FAILURE;
    }
    PR_INIT_CLIST(&node->link);
    node->data = data;
    if (list->head) {
	if (list->sortFunc) {
	    PRCList *link;
	    nssListElement *currNode;
	    currNode = list->head;
	    /* insert in ordered list */
	    while (currNode) {
		link = &currNode->link;
		if (list->sortFunc(data, currNode->data) <= 0) {
		    /* new element goes before current node */
		    PR_INSERT_BEFORE(&node->link, link);
		    /* reset head if this is first */
		    if (currNode == list->head) list->head = node;
		    break;
		}
		if (link == PR_LIST_TAIL(&list->head->link)) {
		    /* reached end of list, append */
		    PR_INSERT_AFTER(&node->link, link);
		    break;
		}
		currNode = (nssListElement *)PR_NEXT_LINK(&currNode->link);
	    }
	} else {
	    /* not sorting */
	    PR_APPEND_LINK(&node->link, &list->head->link);
	}
    } else {
	list->head = node;
    }
    ++list->count;
    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
nssList_Add(nssList *list, void *data)
{
    PRStatus nssrv;
    NSSLIST_LOCK_IF(list);
    nssrv = nsslist_add_element(list, data);
    NSSLIST_UNLOCK_IF(list);
    return PR_SUCCESS;
}

NSS_IMPLEMENT PRStatus
nssList_AddUnique(nssList *list, void *data)
{
    PRStatus nssrv;
    nssListElement *node;
    NSSLIST_LOCK_IF(list);
    node = nsslist_get_matching_element(list, data);
    if (node) {
	/* already in, finish */
	NSSLIST_UNLOCK_IF(list);
	return PR_SUCCESS;
    }
    nssrv = nsslist_add_element(list, data);
    NSSLIST_UNLOCK_IF(list);
    return nssrv;
}

NSS_IMPLEMENT PRStatus
nssList_Remove(nssList *list, void *data)
{
    nssListElement *node;
    NSSLIST_LOCK_IF(list);
    node = nsslist_get_matching_element(list, data);
    if (node) {
	if (node == list->head) {
	    list->head = (nssListElement *)PR_NEXT_LINK(&node->link);
	}
	PR_REMOVE_LINK(&node->link);
	nss_ZFreeIf(node);
	if (--list->count == 0) {
	    list->head = NULL;
	}
    }
    NSSLIST_UNLOCK_IF(list);
    return PR_SUCCESS;
}

NSS_IMPLEMENT void *
nssList_Get(nssList *list, void *data)
{
    nssListElement *node;
    NSSLIST_LOCK_IF(list);
    node = nsslist_get_matching_element(list, data);
    NSSLIST_UNLOCK_IF(list);
    return (node) ? node->data : NULL;
}

NSS_IMPLEMENT PRUint32
nssList_Count(nssList *list)
{
    return list->count;
}

NSS_IMPLEMENT PRStatus
nssList_GetArray(nssList *list, void **rvArray, PRUint32 maxElements)
{
    nssListElement *node;
    PRUint32 i = 0;
    PR_ASSERT(maxElements > 0);
    node = list->head;
    if (!node) {
	return PR_SUCCESS;
    }
    NSSLIST_LOCK_IF(list);
    while (node) {
	rvArray[i++] = node->data;
	if (i == maxElements) break;
	node = (nssListElement *)PR_NEXT_LINK(&node->link);
	if (node == list->head) {
	    break;
	}
    }
    NSSLIST_UNLOCK_IF(list);
    return PR_SUCCESS;
}

NSS_IMPLEMENT nssList *
nssList_Clone(nssList *list)
{
    nssList *rvList;
    nssListElement *node;
    rvList = nssList_Create(NULL, (list->lock != NULL));
    if (!rvList) {
	return NULL;
    }
    NSSLIST_LOCK_IF(list);
    if (list->count > 0) {
	node = list->head;
	while (PR_TRUE) {
	    nssList_Add(rvList, node->data);
	    node = (nssListElement *)PR_NEXT_LINK(&node->link);
	    if (node == list->head) {
		break;
	    }
	}
    }
    NSSLIST_UNLOCK_IF(list);
    return rvList;
}

NSS_IMPLEMENT nssListIterator *
nssList_CreateIterator(nssList *list)
{
    nssListIterator *rvIterator;
    rvIterator = nss_ZNEW(NULL, nssListIterator);
    if (!rvIterator) {
	return NULL;
    }
    rvIterator->list = nssList_Clone(list);
    if (!rvIterator->list) {
	nss_ZFreeIf(rvIterator);
	return NULL;
    }
    rvIterator->current = rvIterator->list->head;
    if (list->lock) {
	rvIterator->lock = PZ_NewLock(nssILockOther);
	if (!rvIterator->lock) {
	    nssList_Destroy(rvIterator->list);
	    nss_ZFreeIf(rvIterator);
	    rvIterator = NULL;
	}
    }
    return rvIterator;
}

NSS_IMPLEMENT void
nssListIterator_Destroy(nssListIterator *iter)
{
    if (iter->lock) {
	(void)PZ_DestroyLock(iter->lock);
    }
    nssList_Destroy(iter->list);
    nss_ZFreeIf(iter);
}

NSS_IMPLEMENT void *
nssListIterator_Start(nssListIterator *iter)
{
    NSSLIST_LOCK_IF(iter);
    if (iter->list->count == 0) {
	return NULL;
    }
    iter->current = iter->list->head;
    return iter->current->data;
}

NSS_IMPLEMENT void *
nssListIterator_Next(nssListIterator *iter)
{
    nssListElement *node;
    PRCList *link;
    if (iter->list->count == 1 || iter->current == NULL) {
	/* Reached the end of the list.  Don't change the state, force to
	 * user to call nssList_Finish to clean up.
	 */
	return NULL;
    }
    node = (nssListElement *)PR_NEXT_LINK(&iter->current->link);
    link = &node->link;
    if (link == PR_LIST_TAIL(&iter->list->head->link)) {
	/* Signal the end of the list. */
	iter->current = NULL;
	return node->data;
    }
    iter->current = node;
    return node->data;
}

NSS_IMPLEMENT PRStatus
nssListIterator_Finish(nssListIterator *iter)
{
    iter->current = iter->list->head;
    return (iter->lock) ? PZ_Unlock(iter->lock) : PR_SUCCESS;
}

