/*
 * Copyright (c) 1997, 1998, 1999, 2001, 2002
 *      The Regents of the University of California.  All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that: (1) source code distributions
 * retain the above copyright notice and this paragraph in its entirety, (2)
 * distributions including binary code include the above copyright notice and
 * this paragraph in its entirety in the documentation or other materials
 * provided with the distribution, and (3) all advertising materials mentioning
 * features or use of this software display the following acknowledgement:
 * ``This product includes software developed by the University of California,
 * Lawrence Berkeley Laboratory and its contributors.'' Neither the name of
 * the University nor the names of its contributors may be used to endorse
 * or promote products derived from this software without specific prior
 * written permission.
 * THIS SOFTWARE IS PROVIDED ``AS IS'' AND WITHOUT ANY EXPRESS OR IMPLIED
 * WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.
 */

#ifndef lint
static const char rcsid[] =
    "@(#) $Id: cq.c,v 1.19 2002/10/11 02:28:50 leres Exp $ (LBL)";
#endif

#include <sys/types.h>

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#ifdef HAVE_MEMORY_H
#include <memory.h>
#endif
#include <math.h>

#ifdef CQ_DEVELOPMENT
#include <lbl/gnuc.h>
#include <lbl/os-proto.h>
#endif

#include "cq.h"

/* Priority to virtual bucket (int) */
#define PRI2VBUCKET(hp, p) ((int)((p) / (hp)->bwidth))

/* Priority to bucket (int) */
#define PRI2BUCKET(hp, p) \
    ((int)fmod((p) / (hp)->bwidth, (double)((hp)->nbuckets)))

/* Priority to bucket top (double) */
#define PRI2BUCKETTOP(hp, p) ((hp)->bwidth * (((p) / (hp)->bwidth) + 1.5))

/* True if bucket is in use */
#define BUCKETINUSE(bp) ((bp)->cookie != NULL)

/* Private data */
struct cq_handle {
	int nbuckets;			/* number of buckets */
	int qlen;			/* number of queued entries */
	int max_qlen;			/* max. number of queued entries */
	int himark;			/* high bucket threshold */
	int lowmark;			/* low bucket threshold */
	int nextbucket;			/* next bucket to check */
	int noresize;			/* don't resize while we're resizing */
	double lastpri;			/* last priority */
	double ysize;			/* length of a year */
	double bwidth;			/* width of each bucket */
	double buckettop;		/* priority of top of current bucket */
	struct cq_bucket *buckets;	/* array of buckets */
};

struct cq_bucket {
	double pri;
	void *cookie;
	struct cq_bucket *next;
};

#ifdef DEBUG
#ifdef CQ_DEVELOPMENT
extern int debug;
#else
int debug = 1;
#endif
#endif

static struct cq_bucket *free_list = 0;

/* Forwards */
static int cq_resize(struct cq_handle *, int);
static void cq_destroybuckets(struct cq_bucket *, int);
#ifdef DEBUG
static int cq_debugbucket(struct cq_handle *, struct cq_bucket *);
void cq_debug(struct cq_handle *, int);
#endif


/* Initialize a calendar queue */
struct cq_handle *
cq_init(register double ysize, register double placebo)
{
	register struct cq_handle *hp;

#ifdef DEBUG
	if (debug > 1)
		fprintf(stderr, "cq_init(%f)\n", ysize);
#endif
	/* The year size be positive */
	if (ysize <= 0.0) {
		errno = EINVAL;
		return (NULL);
	}

	/* Allocate handle */
	hp = (struct cq_handle *)malloc(sizeof(*hp));
	if (hp == NULL)
		return (NULL);
	memset(hp, 0, sizeof(*hp));

	/* Initialize handle */
	hp->ysize = ysize;
	hp->max_qlen = 0;

	/* Allocate initial buckets and finish handle initialization */
	if (cq_resize(hp, 0) < 0) {
		free(hp);
		return (NULL);
	}
	return (hp);
}

/* Returns zero on success, -1 on error (with errno set) */
int
cq_enqueue(register struct cq_handle *hp, register double pri,
    register void *cookie)
{
	register struct cq_bucket *bp, *bp2;
#ifdef DEBUG
	register int q1, q2;
	register struct cq_bucket *buckethead;
#endif

#ifdef DEBUG
	if (debug > 1)
		fprintf(stderr, "cq_enqueue(%f)\n", pri);
#endif

	/* The priority must be positive and the cookie non-null */
	if (pri <= 0.0 || cookie == NULL) {
		errno = EINVAL;
		return (-1);
	}

	/* We might as well resize now if we're going to need to */
	if (hp->qlen + 1 > hp->himark && cq_resize(hp, 1) < 0)
		return (-1);

	bp = hp->buckets + PRI2BUCKET(hp, pri);
#ifdef DEBUG
	if (debug) {
		buckethead = bp;
		q1 = cq_debugbucket(hp, buckethead);
	} else {
		buckethead = NULL;
		q1 = 0;
	}
#endif
	if (BUCKETINUSE(bp)) {
		/* Allocate chained bucket */
		if (free_list) {
			bp2 = free_list;
			free_list = free_list->next;
		} else {
			bp2 = (struct cq_bucket *)malloc(sizeof(*bp2));
			if (bp2 == NULL)
				return (-1);
		}
		memset(bp2, 0, sizeof(*bp2));
		if (pri < bp->pri) {
			/* Insert new bucket at head of queue */
			*bp2 = *bp;
			bp->next = bp2;
		} else {
			/* Insert entry in order (fifo when pri's are equal) */
			while (bp->next != NULL && pri >= bp->next->pri)
				bp = bp->next;
			bp2->next = bp->next;
			bp->next = bp2;
			bp = bp2;
		}
	}
	bp->pri = pri;
	bp->cookie = cookie;
	if (++hp->qlen > hp->max_qlen)
		hp->max_qlen = hp->qlen;
#ifdef DEBUG
	if (debug) {
		q2 = cq_debugbucket(hp, buckethead);
		if (q1 + 1 != q2) {
			fprintf(stderr, "enqueue length wrong\n");
			cq_dump(hp);
			abort();
		}
	}
#endif

	/* If new priority is old, we need to recalculate nextbucket */
	if (hp->lastpri == 0.0 || hp->lastpri > pri) {
		hp->lastpri = pri;
		hp->nextbucket = PRI2BUCKET(hp, hp->lastpri);
		hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri);
	}
#ifdef notdef
	if (debug)
		cq_debug(hp, 0);
#endif
	return (0);
}

void *
cq_dequeue(register struct cq_handle *hp, double pri)
{
	register int n;
	register struct cq_bucket *bp, *bp2, *lowbp;
	register void *cookie;

#ifdef DEBUG
	if (debug > 1)
		fprintf(stderr, "cq_dequeue(%f)\n", pri);
#endif
	if (pri < hp->lastpri)
		/* For sure nothing to do. */
		return (NULL);

	lowbp = NULL;
	for (n = hp->nbuckets, bp = hp->buckets + hp->nextbucket; n > 0; --n) {
		/* Check bucket if it contains an entry (in the current year) */
		if (BUCKETINUSE(bp)) {
			if (bp->pri < hp->buckettop) {
				/* Check first entry in this bucket */
				if (pri >= bp->pri) {
					cookie = bp->cookie;
					hp->lastpri = bp->pri;
					/* Shouldn't nextbucket now point here?? */
					hp->nextbucket = PRI2BUCKET(hp, hp->lastpri);
					hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri);
					if (bp->next == NULL) {
						/* Zero out first entry */
						bp->pri = 0.0;
						bp->cookie = NULL;
					} else {
						/* Update 1st entry with next */
						bp2 = bp->next;
						*bp = *bp2;
						bp2->next = free_list;
						free_list = bp2;
						/* free(bp2); */
					}
					--hp->qlen;
					if (hp->qlen < hp->lowmark)
						(void)cq_resize(hp, 0);
#ifdef notdef
					if (debug)
						cq_debug(hp, 0);
#endif
					return (cookie);
				}

				/* The first entry is in the current year
				 * but comes after pri.  This means we're
				 * not going to find *any* subsequent entries
				 * that come before pri.  So we're done.
				 */
				hp->lastpri = bp->pri;
				hp->nextbucket = PRI2BUCKET(hp, hp->lastpri);
				hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri);
				return (NULL);
#if 0
				/* Search linked list */
				/* Why is this necessary?  Since the list
				 * is sorted, and we already know that the
				 * first entry has too high a priority,
				 * none of the others can be ready to
				 * dequeue, right??
				 */
				for (bp2 = bp; (bp3 = bp2->next) != NULL;
				    bp2 = bp3) {
					/* Don't look past end of bucket */
					if (bp3->pri >= hp->buckettop)
						break;
					if (pri >= bp3->pri) {
						/* Unlink entry */
						cookie = bp3->cookie;
						hp->lastpri = bp->pri;
						bp2->next = bp3->next;
						free(bp3);
						--hp->qlen;
						if (hp->qlen < hp->lowmark)
							(void)cq_resize(hp, 0);
						return (cookie);
					}
				}
#endif
			}

			/* Keep track of lowest priority bucket */
			if (lowbp == NULL || lowbp->pri > bp->pri)
				lowbp = bp;
		}

		/* Step to next bucket */
		hp->buckettop += hp->bwidth;
		++hp->nextbucket;
		if (hp->nextbucket < hp->nbuckets)
			++bp;
		else {
			bp = hp->buckets;
			hp->nextbucket = 0;
		}
	}

	/*
	 * If we got here, we checked all the buckets but came up
	 * empty. This can happen when the queued priorities are
	 * really sparse (e.g. when there is more than a year
	 * between two adjacent entries).
	 *
	 * If there was at least one bucket in use, check to see
	 * if it's the one we're looking for. Also, update nextbucket
	 * (and buckettop) with this bucket.
	 */
	if (lowbp != NULL) {
		cookie = NULL;
		bp = lowbp;
		if (pri >= bp->pri) {
			cookie = bp->cookie;
			if (bp->next == NULL) {
				/* Zero out first entry */
				bp->pri = 0.0;
				bp->cookie = NULL;
			} else {
				/* Update 1st entry with next */
				bp2 = bp->next;
				*bp = *bp2;
				bp2->next = free_list;
				free_list = bp2;
				/* free(bp2); */
			}
			--hp->qlen;
			/* If we resize, we don't need to update */
			if (hp->qlen < hp->lowmark) {
				(void)cq_resize(hp, 0);
				return (cookie);
			}
		}
		hp->lastpri = lowbp->pri;
		hp->nextbucket = PRI2BUCKET(hp, hp->lastpri);
		hp->buckettop = PRI2BUCKETTOP(hp, hp->lastpri);
		if (cookie != NULL)
			return (cookie);
	}

	/* Checked all buckets */
	return (NULL);
}

int
cq_size(struct cq_handle *hp)
{
	return hp->qlen;
}

int
cq_max_size(struct cq_handle *hp)
{
	return hp->max_qlen;
}

/* Return without doing anything if we fail to allocate a new bucket array */
static int
cq_resize(register struct cq_handle *hp, register int grow)
{
	register int n, nbuckets, oldnbuckets;
	register size_t size;
	register struct cq_bucket *bp, *bp2, *buckets, *oldbuckets;
	struct cq_handle savedhandle;

	if (hp->noresize)
		return (0);
#ifdef DEBUG
	if (debug)
		cq_debug(hp, 0);
#endif

	/* Save old bucket array */
	oldnbuckets = hp->nbuckets;
	oldbuckets = hp->buckets;

	/* If no old buckets, we're initializing */
	if (oldbuckets == NULL)
		nbuckets = 2;
	else if (grow)
		nbuckets = oldnbuckets * 2;
	else
		nbuckets = oldnbuckets / 2;

	/* XXX could check that nbuckets is a power of 2 */

	size = sizeof(*buckets) * nbuckets;
	buckets = (struct cq_bucket *)malloc(size);
	if (buckets == NULL)
		return (-1);
	memset(buckets, 0, size);

	/* Save a copy of the handle in case we have dynamic memory problems */
	savedhandle = *hp;

	/* Install new bucket array */
	hp->nbuckets = nbuckets;
	hp->buckets = buckets;

	/* Initialize other parameters */
	hp->himark = hp->nbuckets * 1.5;
	hp->lowmark = (hp->nbuckets / 2) - 2;
	hp->bwidth = hp->ysize / (double)hp->nbuckets;

	/* Tell cq_enqueue() to update nextbucket and buckettop */
	hp->lastpri = 0.0;

#ifdef DEBUG
	if (debug > 1)
		fprintf(stderr,
		    "buckets 0x%lx, nbuckets %d, bwidth %f, buckettop %f\n",
		    (u_long)hp->buckets,
		    hp->nbuckets,
		    hp->bwidth, hp->buckettop);
#endif

	/* We're done if we were just initializing */
	if (oldbuckets == NULL)
		return (0);

	/* Insert entries from old bucket array into new one */
	++hp->noresize;
	hp->qlen = 0;
	for (bp = oldbuckets, n = oldnbuckets; n > 0; --n, ++bp)
		if (BUCKETINUSE(bp))
			for (bp2 = bp; bp2 != NULL; bp2 = bp2->next) {
				if (cq_enqueue(hp, bp2->pri, bp2->cookie) < 0) {
					/* Bummer! */
					*hp = savedhandle;
					/* hp->resize restored */
					cq_destroybuckets(buckets, nbuckets);
					free(buckets);
					return (-1);
				}
			}
	--hp->noresize;

	cq_destroybuckets(oldbuckets, oldnbuckets);
	free(oldbuckets);
#ifdef DEBUG
	if (debug)
		cq_debug(hp, 0);
#endif
	return (0);
}

static void
cq_destroybuckets(register struct cq_bucket *buckets, register int n)
{
	register struct cq_bucket *bp, *bp2, *bp3;

	for (bp = buckets; n > 0; --n, ++bp) {
		bp2 = bp->next;
		while (bp2 != NULL) {
			bp3 = bp2->next;
			bp2->next = free_list;
			free_list = bp2;
			/* free(bp2); */
			bp2 = bp3;
		}
	}
}

/* Destroy a calendar queue */
void
cq_destroy(register struct cq_handle *hp)
{

	cq_destroybuckets(hp->buckets, hp->nbuckets);
	while (free_list) {
		struct cq_bucket *next_free = free_list->next;
		free(free_list);
		free_list = next_free;
	}
	free(hp->buckets);
	free(hp);
}

#ifdef DEBUG
static int
cq_debugbucket(register struct cq_handle *hp,
    register struct cq_bucket *buckets)
{
	register int qlen;
	register struct cq_bucket *bp, *bp2;
	double pri;

	qlen = 0;
	pri = 0.0;
	for (bp = buckets; bp != NULL; bp = bp->next) {
		if (BUCKETINUSE(bp)) {
			++qlen;
			bp2 = hp->buckets + PRI2BUCKET(hp, bp->pri);
			if (bp2 != buckets) {
				fprintf(stderr,
				    "%f in wrong bucket! (off by %d)\n",
				    bp->pri, bp2 - buckets);
				cq_dump(hp);
				abort();
			}
			if (bp->pri < pri) {
				fprintf(stderr,
				    "bad pri order %f < %f (qlen %d)\n",
				    bp->pri, pri, qlen);
				cq_dump(hp);
				abort();
			}
			pri = bp->pri;
		}
	}
	return (qlen);
}

void
cq_debug(register struct cq_handle *hp, register int print)
{
	register int n, qlen, xnextbucket, nextbucket;
	register struct cq_bucket *bp, *lowbp;
	register double xbuckettop, buckettop;

	qlen = 0;
	lowbp = NULL;
	bp = hp->buckets + hp->nextbucket;
	for (n = hp->nbuckets; n > 0; --n) {
		if (BUCKETINUSE(bp) && (lowbp == NULL || lowbp->pri > bp->pri))
			lowbp = bp;

		qlen += cq_debugbucket(hp, bp);

		/* Step to next bucket */
		++bp;
		if (bp >= hp->buckets + hp->nbuckets)
			bp = hp->buckets;
	}

	if (lowbp != NULL) {
		/* We expect lastpri gt or eq to the lowest queued priority */
		if (lowbp->pri < hp->lastpri) {
			fprintf(stderr, "lastpri wacked (%f < %f)\n",
			    lowbp->pri, hp->lastpri);
			cq_dump(hp);
			abort();
		}

		/* Must search for the next entry just as cq_dequeue() would */
		nextbucket = hp->nextbucket;
		buckettop = hp->buckettop;
		bp = hp->buckets + nextbucket;
		for (n = hp->nbuckets; n > 0; --n) {
			if (BUCKETINUSE(bp) && bp->pri < buckettop)
				break;

			/* Step to next bucket */
			++bp;
			++nextbucket;
			buckettop += hp->bwidth;
			if (bp >= hp->buckets + hp->nbuckets) {
				bp = hp->buckets;
				nextbucket = 0;
			}
		}

		xnextbucket = PRI2BUCKET(hp, lowbp->pri);
		if (xnextbucket != nextbucket) {
			fprintf(stderr, "nextbucket wacked (%d != %d)\n",
			     xnextbucket, nextbucket);
			cq_dump(hp);
			abort();
		}

		xbuckettop = PRI2BUCKETTOP(hp, lowbp->pri);
		if (xbuckettop != buckettop) {
			fprintf(stderr, "buckettop wacked (%f != %f)\n",
			    xbuckettop, buckettop);
			cq_dump(hp);
			abort();
		}
	}
	if (qlen != hp->qlen) {
		fprintf(stderr, "qlen wacked (%d != %d)\n", qlen, hp->qlen);
		cq_dump(hp);
		abort();
	}
}

void
cq_dump(register struct cq_handle *hp)
{
	register struct cq_bucket *bp, *bp2;
	register int n;

	fprintf(stderr,
	    "\ncq_dump(): nbucket %d, qlen %d, nextbucket %d, lastpri %f\n",
	    hp->nbuckets, hp->qlen, hp->nextbucket, hp->lastpri);
	fprintf(stderr, "\tysize %f, bwidth %f, buckettop %f\n",
	    hp->ysize, hp->bwidth, hp->buckettop);

	bp = hp->buckets;
	for (n = 0, bp = hp->buckets; n < hp->nbuckets; ++n, ++bp) {
		fprintf(stderr, "  %c %2d: %f (0x%lx)\n",
		    (n == hp->nextbucket) ? '+' : ' ', n,
		    bp->pri, (u_long)bp->cookie);
		for (bp2 = bp->next; bp2 != NULL; bp2 = bp2->next)
			fprintf(stderr, "        %f (0x%lx)\n",
			    bp2->pri, (u_long)bp2->cookie);
	}
}
#endif
