/* -*- mode: c; c-basic-offset: 4; tab-width: 4; indent-tabs-mode: t -*-
 * vim: ts=4 sw=4 noet ai cindent syntax=c
 *
 * prioqueue:  a simple priority queue implementation
 *
 * The queue organises it's data internally using a doubly linked
 * list, into which elements are inserted at the right position. This
 * is definitely not the best algorithm for a priority queue, but it
 * fits best for the given purpose, i.e. the top process sorting.
 * This means we have a rather little amount of total elements (~200
 * on a normal system), which are to be inserted into a queue of only
 * the few top-most elements (10 at the current state). Additionally,
 * at each update interval, the queue is drained completely and
 * refilled from scratch.
 *
 * Copyright (C) 2009 Phil Sutter <phil@nwl.cc>
 *
 * Initially based on the former implementation of sorted processes in
 * top.c, Copyright (C) 2005 David Carter <boojit@pundo.com>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * 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, see <http://www.gnu.org/licenses/>.
 *
 */

#include "conky.h"
#include <limits.h>	/* INT_MAX */
#include <stdlib.h>
#include <string.h>

struct prio_elem {
	struct prio_elem *next, *prev;
	void *data;
};

struct prio_queue {
	/* Compare a and b. Return:
	 * <0 if a should come before b,
	 * >0 if b should come before a,
	 *  0 if don't care */
	int (*compare)(void *a, void *b);

	/* Free element payload. Called when dropping elements. */
	void (*free)(void *a);

	/* Maximum size of queue. The first
	 * elements in the list take precedence. */
	int max_size;

	/* The pointers to the actual list. */
	struct prio_elem *head, *tail;

	/* The current number of elements in the list. */
	int cur_size;
};

/* nop callback to save us from conditional calling */
static void pq_free_nop(void *a) { (void)a; }

struct prio_queue *init_prio_queue(void)
{
	struct prio_queue *retval;

	retval = (struct prio_queue *) malloc(sizeof(struct prio_queue));
	memset(retval, 0, sizeof(struct prio_queue));

	/* use pq_free_nop by default */
	retval->free = &pq_free_nop;

	/* Default to maximum possible size as restricted
	 * by the used data type. This also saves us from
	 * checking if caller has set this field or not. */
	retval->max_size = INT_MAX;

	return retval;
}

void pq_set_compare(struct prio_queue *queue, int (*pqcompare)(void *a, void *b))
{
	if (pqcompare)
		queue->compare = pqcompare;
}

void pq_set_free(struct prio_queue *queue, void (*pqfree)(void *a))
{
	if (pqfree)
		queue->free = pqfree;
}

void pq_set_max_size(struct prio_queue *queue, int max_size)
{
	if (max_size >= 0)
		queue->max_size = max_size;
}

int pq_get_cur_size(struct prio_queue *queue)
{
	return queue->cur_size;
}

static struct prio_elem *init_prio_elem(void *data)
{
	struct prio_elem *retval;

	retval = (struct prio_elem *) malloc(sizeof(struct prio_elem));
	memset(retval, 0, sizeof(struct prio_elem));

	retval->data = data;
	return retval;
}

void insert_prio_elem(struct prio_queue *queue, void *data)
{
	struct prio_elem *cur;

	/* queue->compare is a must-have */
	if (!queue->compare)
		return;

	/* empty queue, insert the first item */
	if (!queue->cur_size) {
		queue->cur_size++;
		queue->head = queue->tail = init_prio_elem(data);
		return;
	}

	/* short-cut 1: new item is lower than all others */
	if (queue->compare(queue->tail->data, data) <= 0) {
		if (queue->cur_size < queue->max_size) {
			queue->cur_size++;
			queue->tail->next = init_prio_elem(data);
			queue->tail->next->prev = queue->tail;
			queue->tail = queue->tail->next;
		} else	/* list was already full */
			(*queue->free)(data);
		return;
	}

	/* short-cut 2: we have a new maximum */
	if (queue->compare(queue->head->data, data) >= 0) {
		queue->cur_size++;
		queue->head->prev = init_prio_elem(data);
		queue->head->prev->next = queue->head;
		queue->head = queue->head->prev;
		goto check_cur_size;
	}

	/* find the actual position if short-cuts failed */
	for (cur = queue->head->next; cur; cur = cur->next) {
		if (queue->compare(cur->data, data) >= 0) {
			queue->cur_size++;
			cur->prev->next = init_prio_elem(data);
			cur->prev->next->prev = cur->prev;
			cur->prev->next->next = cur;
			cur->prev = cur->prev->next;
			break;
		}
	}

check_cur_size:
	/* drop the lowest item if queue overrun */
	if (queue->cur_size > queue->max_size) {
		queue->cur_size--;
		queue->tail = queue->tail->prev;
		(*queue->free)(queue->tail->next->data);
		free_and_zero(queue->tail->next);
	}
}

void *pop_prio_elem(struct prio_queue *queue)
{
	struct prio_elem *tmp;
	void *data;

	if (queue->cur_size <= 0)
		return NULL;

	tmp = queue->head;
	data = tmp->data;

	queue->head = queue->head->next;
	queue->cur_size--;
	if (queue->head)
		queue->head->prev = NULL;
	else	/* list is now empty */
		queue->tail = NULL;

	free(tmp);
	return data;
}

void free_prio_queue(struct prio_queue *queue)
{
	void *data;
	while((data = pop_prio_elem(queue)))
		(*queue->free)(data);
	free(queue);
}
