/*
 * (C) 2006-2009 by Pablo Neira Ayuso <pablo@netfilter.org>
 * 
 * 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 2 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, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "queue.h"
#include "event.h"

#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>

static LIST_HEAD(queue_list);	/* list of existing queues */
static uint32_t qobjects_num;	/* number of active queue objects */

struct queue *
queue_create(const char *name, int max_objects, unsigned int flags)
{
	struct queue *b;

	b = calloc(sizeof(struct queue), 1);
	if (b == NULL)
		return NULL;

	b->max_elems = max_objects;
	INIT_LIST_HEAD(&b->head);
	b->flags = flags;

	if (flags & QUEUE_F_EVFD) {
		b->evfd = create_evfd();
		if (b->evfd == NULL) {
			free(b);
			return NULL;
		}
	}
	strncpy(b->name, name, QUEUE_NAMELEN);
	b->name[QUEUE_NAMELEN-1]='\0';
	list_add(&b->list, &queue_list);

	return b;
}

void queue_destroy(struct queue *b)
{
	list_del(&b->list);
	if (b->flags & QUEUE_F_EVFD)
		destroy_evfd(b->evfd);
	free(b);
}

void queue_stats_show(int fd)
{
	struct queue *this;
	int size = 0;
	char buf[512];

	size += snprintf(buf+size, sizeof(buf),
			 "allocated queue nodes:\t\t%12u\n\n",
			 qobjects_num);

	list_for_each_entry(this, &queue_list, list) {
		size += snprintf(buf+size, sizeof(buf),
				 "queue %s:\n"
				 "current elements:\t\t%12u\n"
				 "maximum elements:\t\t%12u\n"
				 "not enough space errors:\t%12u\n\n",
				 this->name,
				 this->num_elems,
				 this->max_elems,
				 this->enospc_err);
	}
	send(fd, buf, size, 0);
}

void queue_node_init(struct queue_node *n, int type)
{
	INIT_LIST_HEAD(&n->head);
	n->type = type;
}

void *queue_node_data(struct queue_node *n)
{
	return ((char *)n) + sizeof(struct queue_node);
}

struct queue_object *queue_object_new(int type, size_t size)
{
	struct queue_object *obj;

	obj = calloc(sizeof(struct queue_object) + size, 1);
	if (obj == NULL)
		return NULL;

	obj->qnode.size = size;
	queue_node_init(&obj->qnode, type);
	qobjects_num++;

	return obj;
}

void queue_object_free(struct queue_object *obj)
{
	free(obj);
	qobjects_num--;
}

int queue_add(struct queue *b, struct queue_node *n)
{
	if (!list_empty(&n->head))
		return 0;

	if (b->num_elems >= b->max_elems) {
		b->enospc_err++;
		errno = ENOSPC;
		return -1;
	}
	n->owner = b;
	list_add_tail(&n->head, &b->head);
	b->num_elems++;
	if (b->evfd)
		write_evfd(b->evfd);
	return 1;
}

int queue_del(struct queue_node *n)
{
	if (list_empty(&n->head))
		return 0;

	list_del_init(&n->head);
	n->owner->num_elems--;
	if (n->owner->evfd)
		read_evfd(n->owner->evfd);
	n->owner = NULL;
	return 1;
}

struct queue_node *queue_del_head(struct queue *b)
{
	struct queue_node *n = (struct queue_node *) b->head.next;
	queue_del(n);
	return n;
}

int queue_in(struct queue *b, struct queue_node *n)
{
	return b == n->owner;
}

int queue_get_eventfd(struct queue *b)
{
	return get_read_evfd(b->evfd);
}

void queue_iterate(struct queue *b, 
		   const void *data, 
		   int (*iterate)(struct queue_node *n, const void *data2))
{
	struct list_head *i, *tmp;
	struct queue_node *n;

	list_for_each_safe(i, tmp, &b->head) {
		n = (struct queue_node *) i;
		if (iterate(n, data))
			break;
	}
}

unsigned int queue_len(const struct queue *b)
{
	return b->num_elems;
}