/*
 * Copyright (c) 1995-2002 Silicon Graphics, Inc.  All Rights Reserved.
 * Copyright (c) 2013-2017 Red Hat, Inc.
 * 
 * This 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 2.1 of the License, or
 * (at your option) any later version.
 * 
 * This 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.
 */

#include "pmapi.h"
#include "libpcp.h"
#include <stddef.h>

void
__pmHashInit(__pmHashCtl *hcp)
{
    memset(hcp, 0, sizeof(*hcp));
    /* NB: some __pmHash* clients rely on this to eschew explicit
       initialization for .bss / .data-resident __pmHashCtl structs. */
}

/*
 * Used to preallocate the hash table when the size is known ahead of time.
 * This avoids the overhead of growing and relinking the hash chains.
 */
int
__pmHashPreAlloc(int hsize, __pmHashCtl *hcp)
{
    if (hcp->hash != NULL) {
	/* too late, or already done before */
	return -1;
    }

    if ((hcp->hash = (__pmHashNode **)calloc(hsize, sizeof(__pmHashNode *))) == NULL)
	return -oserror();

    hcp->hsize = hsize;
    return 0; /* ok */
}

__pmHashNode *
__pmHashSearch(unsigned int key, __pmHashCtl *hcp)
{
    __pmHashNode	*hp;

    if (hcp->hsize == 0)
	return NULL;

    for (hp = hcp->hash[key % hcp->hsize]; hp != NULL; hp = hp->next) {
	if (hp->key == key)
	    return hp;
    }
    return NULL;
}

int
__pmHashAdd(unsigned int key, void *data, __pmHashCtl *hcp)
{
    __pmHashNode    *hp;
    int		k;

    hcp->nodes++;

    if (hcp->hsize == 0) {
	hcp->hsize = 1;	/* arbitrary number */
	if ((hcp->hash = (__pmHashNode **)calloc(hcp->hsize, sizeof(__pmHashNode *))) == NULL) {
	    hcp->hsize = 0;
	    return -oserror();
	}
    }
    else if (hcp->nodes / 4 > hcp->hsize) {
	__pmHashNode	*tp;
	__pmHashNode	**old = hcp->hash;
	int		oldsize = hcp->hsize;

	hcp->hsize *= 2;
	if (hcp->hsize % 2) hcp->hsize++;
	if (hcp->hsize % 3) hcp->hsize += 2;
	if (hcp->hsize % 5) hcp->hsize += 2;
	if ((hcp->hash = (__pmHashNode **)calloc(hcp->hsize, sizeof(__pmHashNode *))) == NULL) {
	    hcp->hsize = oldsize;
	    hcp->hash = old;
	    return -oserror();
	}
	/*
	 * re-link chains
	 */
	while (oldsize) {
	    for (hp = old[--oldsize]; hp != NULL; ) {
		tp = hp;
		hp = hp->next;
		k = tp->key % hcp->hsize;
		tp->next = hcp->hash[k];
		hcp->hash[k] = tp;
	    }
	}
	free(old);
    }

    if ((hp = (__pmHashNode *)malloc(sizeof(__pmHashNode))) == NULL)
	return -oserror();

    k = key % hcp->hsize;
    hp->key = key;
    hp->data = data;
    hp->next = hcp->hash[k];
    hcp->hash[k] = hp;

    return 1;
}

int
__pmHashDel(unsigned int key, void *data, __pmHashCtl *hcp)
{
    __pmHashNode    *hp;
    __pmHashNode    *lhp = NULL;

    if (hcp->hsize == 0)
	return 0;

    for (hp = hcp->hash[key % hcp->hsize]; hp != NULL; hp = hp->next) {
	if (hp->key == key && hp->data == data) {
	    if (lhp == NULL)
		hcp->hash[key % hcp->hsize] = hp->next;
	    else
		lhp->next = hp->next;
	    free(hp);
	    hcp->nodes--;
	    return 1;
	}
	lhp = hp;
    }

    return 0;
}

void
__pmHashClear(__pmHashCtl *hcp)
{
    if (hcp->hsize != 0) {
	free(hcp->hash);
	hcp->hash = NULL;
	hcp->hsize = 0;
    }
}

/*
 * Iterate over the entire hash table.  For each entry, call *cb,
 * passing *cdata and the current key/value pair.  The function's
 * return value decides how to continue or abort iteration.  The
 * callback function must not modify the hash table.
 */
void
__pmHashWalkCB(__pmHashWalkCallback cb, void *cdata, const __pmHashCtl *hcp)
{
    int n;

    for (n = 0; n < hcp->hsize; n++) {
        __pmHashNode *tp = hcp->hash[n];
        __pmHashNode **tpp = & hcp->hash[n];

        while (tp != NULL) {
            __pmHashWalkState state = (*cb)(tp, cdata);

            switch (state) {
            case PM_HASH_WALK_DELETE_STOP:
                *tpp = tp->next;  /* unlink */
                free(tp);         /* delete */
                return;           /* & stop */

            case PM_HASH_WALK_NEXT:
                tpp = &tp->next;
                tp = *tpp;
                break;

            case PM_HASH_WALK_DELETE_NEXT:
                *tpp = tp->next;  /* unlink */
                /* NB: do not change tpp.  It will still point at the previous
                 * node's "next" pointer.  Consider consecutive CONTINUE_DELETEs.
                 */
                free(tp);         /* delete */
                tp = *tpp; /* == tp->next, except that tp is already freed. */
                break;            /* & next */

            case PM_HASH_WALK_STOP:
            default:
                return;
            }
        }
    }
}

/*
 * Walk a hash table; state flow is START ... NEXT ... NEXT ...
 */
__pmHashNode *
__pmHashWalk(__pmHashCtl *hcp, __pmHashWalkState state)
{
    __pmHashNode	*node;

    if (hcp->hsize == 0)
	return NULL;

    if (state == PM_HASH_WALK_START) {
        hcp->index = 0;
        hcp->next = hcp->hash[0];
    }

    while (hcp->next == NULL) {
        hcp->index++;
        if (hcp->index >= hcp->hsize)
            return NULL;
        hcp->next = hcp->hash[hcp->index];
    }

    node = hcp->next;
    hcp->next = node->next;
    return node;
}

/*
 * free __pmHashNode's and reset the hash table to empty
 * ... it is assumed the caller has already walked the hash lists
 * and freed anything hanging off hp->data before calling
 * __pmHashFree()
 */
void
__pmHashFree(__pmHashCtl *hcp)
{
    __pmHashNode	*hp;
    __pmHashNode	*lhp = NULL;
    int			i;

    if (hcp->hsize == 0)
	return;

    for (i = 0; i < hcp->hsize; i++) {
	for (hp = hcp->hash[i]; hp != NULL; hp = hp->next) {
	    if (lhp != NULL)
		free(lhp);
	    lhp = hp;
	}
    }
    if (lhp != NULL)
	free(lhp);

    __pmHashClear(hcp);
}
