/*
 * Copyright (C) 2007 François Pesce : francois.pesce (at) gmail (dot) com
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 * 	http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#ifndef HAVE_APR
#include <stdlib.h>
#include <pthread.h>
#else
#include <apr_thread_mutex.h>
#endif
#include <string.h>

#include "debug.h"
#include "napr_heap.h"

#define INITIAL_MAX 256
#define NAPR_HEAP_PARENT(position) (((position) - 1) >> 1)
#define NAPR_HEAP_LEFT(position)   (((position) << 1) + 1)
#define NAPR_HEAP_RIGHT(position)  (((position) + 1) << 1)

struct napr_heap_t
{
#ifdef HAVE_APR
    apr_pool_t *pool;
    apr_thread_mutex_t *mutex;
#else
    pthread_mutex_t mutex;
#endif
    void **tree;
    napr_heap_cmp_callback_fn_t *cmp;
    napr_heap_display_callback_fn_t *display;
#ifndef HAVE_APR		/* !HAVE_APR */
    /* Then we may specify a function to deallocate data */
    napr_heap_del_callback_fn_t *del;
#endif
    unsigned int count, max;
    int mutex_set;		/* true (i.e. 1) if napr_heap_make_r has been
				   called instead of non-reentrant function */
};

#ifdef HAVE_APR
/* APR_POOL_IMPLEMENT_ACCESSOR(heap); */
apr_pool_t *napr_heap_get_allocator(const napr_heap_t *heap)
{
    return heap->pool;
}
#endif

napr_heap_t *napr_heap_make(
#ifdef HAVE_APR
			       apr_pool_t *pool,
#endif
			       napr_heap_cmp_callback_fn_t *cmp
#ifndef HAVE_APR		/* !HAVE_APR */
			       , napr_heap_del_callback_fn_t *del
#endif
    )
{
    napr_heap_t *heap = NULL;;
#ifdef HAVE_APR
    apr_pool_t *local_pool;

    if (APR_SUCCESS == (apr_pool_create(&local_pool, pool))) {
	heap = apr_palloc(local_pool, sizeof(napr_heap_t));
	heap->pool = local_pool;
	heap->tree = apr_pcalloc(local_pool, sizeof(void *) * INITIAL_MAX);
    }
#else /* !HAVE_APR */
    if (NULL != (heap = malloc(sizeof(napr_heap_t)))) {
	heap->del = del;
	if (NULL == (heap->tree = calloc(INITIAL_MAX, sizeof(void *)))) {
	    free(heap);
	    heap = NULL;
	}
    }
#endif
    if (NULL != heap) {
	heap->max = INITIAL_MAX;
	heap->count = 0;
	heap->cmp = cmp;
	heap->mutex_set = 0;
#ifdef HAVE_APR
	heap->mutex = NULL;
#else
	memset(&(heap->mutex), 0, sizeof(pthread_mutex_t));
#endif
    }

    return heap;
}

void napr_heap_destroy(napr_heap_t *heap)
{
#ifdef HAVE_APR
    apr_pool_destroy(heap->pool);
#else /* !HAVE_APR */
    if (NULL != heap) {
	if (NULL != heap->del && NULL != heap->tree) {
	    unsigned int i;
	    for (i = 0; i < heap->count; i++) {
		heap->del(heap->tree[i]);
	    }
	}
	free(heap->tree);
	free(heap);
    }
#endif
}

int napr_heap_insert(napr_heap_t *heap, void *datum)
{
    void **tmp;
    unsigned int ipos, ppos;

    if (heap->max <= heap->count) {
	/*
	 * reallocation by power of 2:
	 */
	unsigned int new_max;

	for (new_max = 1; new_max <= heap->max; new_max *= 2);

#ifdef HAVE_APR
	tmp = apr_palloc(heap->pool, new_max * sizeof(void *));
#else
	tmp = realloc(heap->tree, new_max * sizeof(void *));
#endif
	if (NULL != tmp) {
#ifdef HAVE_APR
	    memcpy(tmp, (heap->tree), (heap->count) * sizeof(void *));
#endif
	    memset((tmp + (heap->count)), 0, (new_max - (heap->count + 1)) * sizeof(void *));
	    heap->tree = tmp;
	    heap->max = new_max;
	}
	else {
	    heap->tree = NULL;
	    DEBUG_ERR("allocation failed");
	    return -1;
	}
    }

    /*
     * insertion of the datum after the last one of the tree...
     */
    heap->tree[heap->count] = datum;

    ipos = heap->count;
    ppos = NAPR_HEAP_PARENT(ipos);

    while (ipos > 0 && (heap->cmp(heap->tree[ppos], heap->tree[ipos]) < 0)) {
	/*
	 * Swap the value ...
	 */
	tmp = heap->tree[ppos];
	heap->tree[ppos] = heap->tree[ipos];
	heap->tree[ipos] = tmp;

	ipos = ppos;
	ppos = NAPR_HEAP_PARENT(ipos);
    }

    heap->count++;

    return 0;
}

void *napr_heap_extract(napr_heap_t *heap)
{
    void *ret = NULL, *tmp;
    unsigned int ipos, rpos, lpos, mpos;

    if ((0 != heap->count) && (NULL != heap->tree)) {
	/* keep the value to return */
	ret = heap->tree[0];
	/* It works for count == 1 too (just think about it) */
	heap->tree[0] = heap->tree[heap->count - 1];
	heap->tree[heap->count - 1] = NULL;
	heap->count--;
	ipos = 0;

	while (1) {
	    lpos = NAPR_HEAP_LEFT(ipos);
	    rpos = NAPR_HEAP_RIGHT(ipos);

	    if (lpos < heap->count) {
		if (heap->cmp(heap->tree[lpos], heap->tree[ipos]) > 0) {
		    mpos = lpos;
		}
		else {
		    mpos = ipos;
		}
		if ((rpos < heap->count) && (heap->cmp(heap->tree[rpos], heap->tree[mpos])) > 0) {
		    mpos = rpos;
		}
	    }
	    else {
		mpos = ipos;
	    }

	    if (mpos != ipos) {
		/*
		 * Swap the choosen children with the current node
		 */
		tmp = heap->tree[mpos];
		heap->tree[mpos] = heap->tree[ipos];
		heap->tree[ipos] = tmp;
		ipos = mpos;
	    }
	    else {
		break;
	    }
	}
    }

    return ret;
}

void *napr_heap_get_nth(const napr_heap_t *heap, unsigned int n)
{
    if ((n < heap->count) && (NULL != heap->tree))
	return heap->tree[n];
    else
	return NULL;
}

unsigned int napr_heap_size(const napr_heap_t *heap)
{
    return heap->count;
}

/*
 * Reentrant versions of previous functions.
 */
napr_heap_t *napr_heap_make_r(
#ifdef HAVE_APR
				 apr_pool_t *pool,
#endif
				 napr_heap_cmp_callback_fn_t *cmp
#ifndef HAVE_APR		/* !HAVE_APR */
				 , napr_heap_del_callback_fn_t *del
#endif
    )
{
    napr_heap_t *heap;

    if (NULL != (heap = napr_heap_make(
#ifdef HAVE_APR
					  pool,
#endif
					  cmp
#ifndef HAVE_APR		/* !HAVE_APR */
					  , del
#endif
		 ))) {
#ifdef HAVE_APR
	if (APR_SUCCESS != apr_thread_mutex_create(&(heap->mutex), APR_THREAD_MUTEX_DEFAULT, pool))
	    heap = NULL;
#else
	if (0 > pthread_mutex_init(&(heap->mutex), NULL)) {
	    free(heap->tree);
	    free(heap);
	    heap = NULL;
	}
#endif
    }
    heap->mutex_set = 1;

    return heap;
}

int napr_heap_insert_r(napr_heap_t *heap, void *datum)
{
    int rc;

    if (1 == heap->mutex_set) {
#ifdef HAVE_APR
	if (APR_SUCCESS == (rc = apr_thread_mutex_lock(heap->mutex))) {
	    if (0 == (rc = napr_heap_insert(heap, datum)))
		rc = apr_thread_mutex_unlock(heap->mutex);
	}
	else {
	    DEBUG_ERR("locking failed");
	}
#else
	if (0 == (rc = pthread_mutex_lock(&heap->mutex))) {
	    if (0 == (rc = napr_heap_insert(heap, datum)))
		rc = pthread_mutex_unlock(&heap->mutex);
	}
#endif
    }
    else
	rc = -1;

    return rc;
}

void *napr_heap_extract_r(napr_heap_t *heap)
{
    void *result;

    result = NULL;
    if (1 == heap->mutex_set) {
#ifdef HAVE_APR
	if (APR_SUCCESS == apr_thread_mutex_lock(heap->mutex)) {
	    if (NULL != (result = napr_heap_extract(heap)))
		if (APR_SUCCESS != apr_thread_mutex_unlock(heap->mutex)) {
		    DEBUG_ERR("unlocking failed");
		    result = NULL;
		}
	}
	else {
	    DEBUG_ERR("locking failed");
	}
#else
	if (0 == pthread_mutex_lock(&heap->mutex)) {
	    if (NULL != (result = napr_heap_extract(heap)))
		if (0 != pthread_mutex_unlock(&heap->mutex))
		    result = NULL;
	}
#endif
    }

    return result;
}

void napr_heap_set_display_cb(napr_heap_t *heap, napr_heap_display_callback_fn_t display)
{
    heap->display = display;
}

void napr_heap_display(const napr_heap_t *heap)
{
    unsigned int i;

    for (i = 0; i < heap->count; i++) {
	if (((i - 1) % 2) == 0) {
	    printf("\n");
	}
	printf("%u:", i);
	heap->display(heap->tree[i]);
	printf("\t");
    }
    printf("\n");
    fflush(stdout);
}


syntax highlighted by Code2HTML, v. 0.9.1