/************************************************************************
 *
 * SKIPLIST.C - Skiplist functions for use in Nagios event/object lists
 *
 *
 * Notes:
 *
 * These function implement a slightly modified skiplist from that
 * described by William Pugh (ftp://ftp.cs.umd.edu/pub/skipLists/skiplists.pdf).
 * The structures and function were modified to allow the list to act
 * like a priority queue for the Nagios event list/queue(s).  Multiple nodes with
 * the same key value are allowed on the list to accommodate multiple events
 * occurring at the same (second) point in time.  Implemented peek() and pop()
 * functions to allow for quick event queue processing, and a method to delete
 * a specific list item, based on its pointer, rather than its data value.  Again,
 * this is useful for the Nagios event queue.
 *
 * License:
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 as
 * published by the Free Software Foundation.
 *
 * This program 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 this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 ************************************************************************/

#include <unistd.h>
#include <stdlib.h>
#include "skiplist.h"


typedef struct skiplistnode_struct {
	void *data;
	struct skiplistnode_struct *forward[1]; /* this must be the last element of the struct, as we allocate # of elements during runtime*/
	} skiplistnode;

struct skiplist_struct {
	int current_level;
	int max_levels;
	float level_probability;
	unsigned long items;
	int allow_duplicates;
	int append_duplicates;
	int (*compare_function)(void *, void *);
	skiplistnode *head;
	};

unsigned long skiplist_num_items(skiplist *list) {
	return list ? list->items : 0;
	}

static skiplistnode *skiplist_new_node(skiplist *list, int node_levels) {
	skiplistnode *newnode = NULL;
	register int x = 0;

	if(list == NULL)
		return NULL;

	if(node_levels < 0 || node_levels > list->max_levels)
		return NULL;

	/* allocate memory for node + variable number of level pointers */
	if((newnode = (skiplistnode *)malloc(sizeof(skiplistnode) + (node_levels * sizeof(skiplistnode *))))) {

		/* initialize forward pointers */
		for(x = 0; x < node_levels; x++)
			newnode->forward[x] = NULL;

		/* initialize data pointer */
		newnode->data = NULL;
		}

	return newnode;
	}


skiplist *skiplist_new(int max_levels, float level_probability, int allow_duplicates, int append_duplicates, int (*compare_function)(void *, void *)) {
	skiplist *newlist = NULL;

	/* alloc memory for new list structure */
	if((newlist = (skiplist *)malloc(sizeof(skiplist)))) {

		/* initialize levels, etc. */
		newlist->current_level = 0;
		newlist->max_levels = max_levels;
		newlist->level_probability = level_probability;
		newlist->allow_duplicates = allow_duplicates;
		newlist->append_duplicates = append_duplicates;
		newlist->items = 0;
		newlist->compare_function = compare_function;

		/* initialize head node */
		newlist->head = skiplist_new_node(newlist, max_levels);
		}

	return newlist;
	}


static int skiplist_random_level(skiplist *list) {
	int level = 0;
	float r = 0.0;

	if(list == NULL)
		return -1;

	for(level = 0; level < list->max_levels; level++) {
		r = ((float)rand() / (float)RAND_MAX);
		if(r > list->level_probability)
			break;
		}

	return (level >= list->max_levels) ? list->max_levels - 1 : level;
	}


int skiplist_insert(skiplist *list, void *data) {
	skiplistnode **update = NULL;
	skiplistnode *thisnode = NULL;
	skiplistnode *nextnode = NULL;
	skiplistnode *newnode = NULL;
	int level = 0;
	int x = 0;

	if(list == NULL || data == NULL) {
		return SKIPLIST_ERROR_ARGS;
		}

	/* check to make sure we don't have duplicates */
	/* NOTE: this could made be more efficient */
	if(list->allow_duplicates == FALSE) {
		if(skiplist_find_first(list, data, NULL))
			return SKIPLIST_ERROR_DUPLICATE;
		}

	/* initialize update vector */
	if((update = (skiplistnode **)malloc(sizeof(skiplistnode *) * list->max_levels)) == NULL) {
		return SKIPLIST_ERROR_MEMORY;
		}
	for(x = 0; x < list->max_levels; x++)
		update[x] = NULL;

	/* find proper position for insert, remember pointers  with an update vector */
	thisnode = list->head;
	for(level = list->current_level; level >= 0; level--) {

		while((nextnode = thisnode->forward[level])) {
			if(list->append_duplicates == TRUE) {
				if(list->compare_function(nextnode->data, data) > 0)
					break;
				}
			else {
				if(list->compare_function(nextnode->data, data) >= 0)
					break;
				}
			thisnode = nextnode;
			}

		update[level] = thisnode;
		}

	/* get a random level the new node should be inserted at */
	level = skiplist_random_level(list);

	/* we're adding a new level... */
	if(level > list->current_level) {
		/*printf("NEW LEVEL!\n");*/
		list->current_level++;
		level = list->current_level;
		update[level] = list->head;
		}

	/* create a new node */
	if((newnode = skiplist_new_node(list, level)) == NULL) {
		/*printf("NODE ERROR\n");*/
		free(update);
		return SKIPLIST_ERROR_MEMORY;
		}
	newnode->data = data;

	/* update pointers to insert node at proper location */
	do {
		thisnode = update[level];
		newnode->forward[level] = thisnode->forward[level];
		thisnode->forward[level] = newnode;

		}
	while(--level >= 0);

	/* update counters */
	list->items++;

	/* free memory */
	free(update);

	return SKIPLIST_OK;
	}



int skiplist_empty(skiplist *list) {
	skiplistnode *this = NULL;
	skiplistnode *next = NULL;
	int level = 0;

	if(list == NULL)
		return ERROR;

	/* free all list nodes (but not header) */
	for(this = list->head->forward[0]; this != NULL; this = next) {
		next = this->forward[0];
		free(this);
		}

	/* reset level pointers */
	for(level = list->current_level; level >= 0; level--)
		list->head->forward[level] = NULL;

	/* reset list level */
	list->current_level = 0;

	/* reset items */
	list->items = 0;

	return OK;
	}



int skiplist_free(skiplist **list) {
	skiplistnode *this = NULL;
	skiplistnode *next = NULL;

	if(list == NULL)
		return ERROR;
	if(*list == NULL)
		return OK;

	/* free header and all list nodes */
	for(this = (*list)->head; this != NULL; this = next) {
		next = this->forward[0];
		free(this);
		}

	/* free list structure */
	free(*list);
	*list = NULL;

	return OK;
	}



/* get first item in list */
void *skiplist_peek(skiplist *list) {

	if(list == NULL)
		return NULL;

	/* return first item */
	return list->head->forward[0]->data;
	}



/* get/remove first item in list */
void *skiplist_pop(skiplist *list) {
	skiplistnode *thisnode = NULL;
	void *data = NULL;
	int level = 0;

	if(list == NULL)
		return NULL;

	/* get first item */
	thisnode = list->head->forward[0];
	if(thisnode == NULL)
		return NULL;

	/* get data for first item */
	data = thisnode->data;

	/* remove first item from queue - update forward links from head to first node */
	for(level = 0; level <= list->current_level; level++) {
		if(list->head->forward[level] == thisnode)
			list->head->forward[level] = thisnode->forward[level];
		}

	/* free deleted node */
	free(thisnode);

	/* adjust items */
	list->items--;

	return data;
	}



/* get first item in list */
void *skiplist_get_first(skiplist *list, void **node_ptr) {
	skiplistnode *thisnode = NULL;

	if(list == NULL)
		return NULL;

	/* get first node */
	thisnode = list->head->forward[0];

	/* return pointer to node */
	if(node_ptr)
		*node_ptr = (void *)thisnode;

	if(thisnode)
		return thisnode->data;
	else
		return NULL;
	}



/* get next item in list */
void *skiplist_get_next(void **node_ptr) {
	skiplistnode *thisnode = NULL;
	skiplistnode *nextnode = NULL;

	if(node_ptr == NULL || *node_ptr == NULL)
		return NULL;

	thisnode = (skiplistnode *)(*node_ptr);
	nextnode = thisnode->forward[0];

	*node_ptr = (void *)nextnode;

	if(nextnode)
		return nextnode->data;
	else
		return NULL;
	}



/* find first item in list */
void *skiplist_find_first(skiplist *list, void *data, void **node_ptr) {
	skiplistnode *thisnode = NULL;
	skiplistnode *nextnode = NULL;
	int level = 0;

	if(list == NULL || data == NULL)
		return NULL;

	thisnode = list->head;
	for(level = list->current_level; level >= 0; level--) {
		while((nextnode = thisnode->forward[level])) {
			if(list->compare_function(nextnode->data, data) >= 0)
				break;
			thisnode = nextnode;
			}
		}

	/* we found it! */
	if(nextnode && list->compare_function(nextnode->data, data) == 0) {
		if(node_ptr)
			*node_ptr = (void *)nextnode;
		return nextnode->data;
		}
	else {
		if(node_ptr)
			*node_ptr = NULL;
		}

	return NULL;
	}



/* find next match */
void *skiplist_find_next(skiplist *list, void *data, void **node_ptr) {
	skiplistnode *thisnode = NULL;
	skiplistnode *nextnode = NULL;

	if(list == NULL || data == NULL || node_ptr == NULL)
		return NULL;
	if(*node_ptr == NULL)
		return NULL;

	thisnode = (skiplistnode *)(*node_ptr);
	nextnode = thisnode->forward[0];

	if(nextnode) {
		if(list->compare_function(nextnode->data, data) == 0) {
			*node_ptr = (void *)nextnode;
			return nextnode->data;
			}
		}

	*node_ptr = NULL;
	return NULL;
	}



/* delete first matching item from list */
int skiplist_delete_first(skiplist *list, void *data) {
	skiplistnode **update = NULL;
	skiplistnode *thisnode = NULL;
	skiplistnode *nextnode = NULL;
	int level = 0;
	int top_level = 0;
	int deleted = FALSE;
	int x = 0;

	if(list == NULL || data == NULL)
		return ERROR;

	/* initialize update vector */
	if((update = (skiplistnode **)malloc(sizeof(skiplistnode *) * list->max_levels)) == NULL)
		return ERROR;
	for(x = 0; x < list->max_levels; x++)
		update[x] = NULL;

	/* find location in list */
	thisnode = list->head;
	for(top_level = level = list->current_level; level >= 0; level--) {
		while((nextnode = thisnode->forward[level])) {
			if(list->compare_function(nextnode->data, data) >= 0)
				break;
			thisnode = nextnode;
			}
		update[level] = thisnode;
		}

	/* we found a match! */
	if(list->compare_function(nextnode->data, data) == 0) {

		/* adjust level pointers to bypass (soon to be) removed node */
		for(level = 0; level <= top_level; level++) {

			thisnode = update[level];
			if(thisnode->forward[level] != nextnode)
				break;

			thisnode->forward[level] = nextnode->forward[level];
			}

		/* free node memory */
		free(nextnode);

		/* adjust top/current level of list is necessary */
		while(list->head->forward[top_level] == NULL && top_level > 0)
			top_level--;
		list->current_level = top_level;

		/* adjust items */
		list->items--;

		deleted = TRUE;
		}

	/* free memory */
	free(update);

	return deleted;
	}



/* delete all matching items from list */
int skiplist_delete(skiplist *list, void *data) {
	int deleted = 0;
	int total_deleted = 0;

	/* NOTE: there is a more efficient way to do this... */
	while((deleted = skiplist_delete_first(list, data)) == 1)
		total_deleted++;

	return total_deleted;
	}



/* delete specific node from list */
int skiplist_delete_node(skiplist *list, void *node_ptr) {
	void *data = NULL;
	skiplistnode **update = NULL;
	skiplistnode *thenode = NULL;
	skiplistnode *thisnode = NULL;
	skiplistnode *nextnode = NULL;
	int level = 0;
	int top_level = 0;
	int deleted = FALSE;
	int x = 0;

	if(list == NULL || node_ptr == NULL)
		return ERROR;

	/* we'll need the data from the node to first find the node */
	thenode = (skiplistnode *)node_ptr;
	data = thenode->data;

	/* initialize update vector */
	if((update = (skiplistnode **)malloc(sizeof(skiplistnode *) * list->max_levels)) == NULL)
		return ERROR;
	for(x = 0; x < list->max_levels; x++)
		update[x] = NULL;

	/* find location in list */
	thisnode = list->head;
	for(top_level = level = list->current_level; level >= 0; level--) {
		while((nextnode = thisnode->forward[level])) {

			/* next node would be too far */
			if(list->compare_function(nextnode->data, data) > 0)
				break;
			/* this is the exact node we want */
			if(list->compare_function(nextnode->data, data) == 0 && nextnode == thenode)
				break;

			thisnode = nextnode;
			}
		update[level] = thisnode;
		}

	/* we found a match! (value + pointers match) */
	if(nextnode && list->compare_function(nextnode->data, data) == 0 && nextnode == thenode) {

		/* adjust level pointers to bypass (soon to be) removed node */
		for(level = 0; level <= top_level; level++) {

			thisnode = update[level];
			if(thisnode->forward[level] != nextnode)
				break;

			thisnode->forward[level] = nextnode->forward[level];
			}

		/* free node memory */
		free(nextnode);

		/* adjust top/current level of list is necessary */
		while(list->head->forward[top_level] == NULL && top_level > 0)
			top_level--;
		list->current_level = top_level;

		/* adjust items */
		list->items--;

		deleted = TRUE;
		}

	/* free memory */
	free(update);

	return deleted;
	}



