/*
 *   ALSA sequencer FIFO
 *   Copyright (c) 1998 by Frank van de Pol <frank@vande-pol.demon.nl>
 *
 *
 *   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 "driver.h"
#include "seq_fifo.h"


/* FIFO */

/* create new fifo */
fifo_t *snd_seq_fifo_new(void)
{
	fifo_t *f;

	f = snd_calloc(sizeof(fifo_t));
	if (f == NULL) {
		snd_printd("malloc failed for snd_seq_fifo_new() \n");
		return NULL;
	}

	f->pool = snd_seq_pool_new();
	if (f->pool == NULL) {
		snd_free(f, sizeof(fifo_t));
		return NULL;
	}
	if (snd_seq_pool_init(f->pool, SND_SEQ_MAX_CLIENT_EVENTS) < 0) {
		snd_seq_pool_delete(&f->pool);
		snd_free(f, sizeof(fifo_t));
		return NULL;
	}

	snd_spin_prepare(f, lock);
	snd_sleep_prepare(f, input);
	f->flags = 0;

	f->head = NULL;
	f->tail = NULL;
	f->cells = 0;
	
	return f;
}

void snd_seq_fifo_delete(fifo_t **fifo)
{
	fifo_t *f;

	if (fifo == NULL) {
		snd_printd("oops: snd_seq_fifo_delete() called with NULL pointer to fifo\n");
		return;
	}
	f = *fifo;
	if (fifo == NULL) {
		snd_printd("oops: snd_seq_fifo_delete() called with NULL fifo\n");
		return;
	}
	*fifo = NULL;

	/* release resources...*/
	/*....................*/

	if (f->pool) {
		snd_seq_pool_done(f->pool);
		snd_seq_pool_delete(&f->pool);
	}
	
	if (f->cells > 0) {
		/* drain the fifo */
		int max = 0;

                while (snd_seq_fifo_avail(f) && max++ < SND_SEQ_MAX_EVENTS)
			snd_seq_cell_free(snd_seq_fifo_cell_out(f));
		if (max >= SND_SEQ_MAX_EVENTS)
			snd_printd("oops: snd_seq_fifo_delete() drain problem\n");
	}
	
	snd_free(f,sizeof(fifo_t));
}


/* enqueue cell to fifo */
void snd_seq_fifo_cell_in(fifo_t * f, snd_seq_event_cell_t * cell)
{
	unsigned long flags;

	if (f == NULL) {
		snd_printd("oops: snd_seq_fifo_cell_in() called with NULL fifo\n");
		return;
	}
	if (cell == NULL) {
		snd_printd("oops: snd_seq_fifo_cell_in() called with NULL cell\n");
		return;
	}

	snd_spin_lock(f, lock, &flags);

	/* add new cell to tail of the fifo */
	if (f->tail != NULL)
		f->tail->ptr_l = cell;
	f->tail = cell;
	cell->ptr_l = NULL;

	/* register if this is the first element */
	if (f->head == NULL)
		f->head = cell;

	f->cells++;
	snd_spin_unlock(f, lock, &flags);

	/* wakeup client */
	if (f->flags & SND_WK_SLEEP) {
		f->flags &= ~SND_WK_SLEEP;
		snd_wakeup(f, input);
	}
}


/* dequeue cell from fifo */
snd_seq_event_cell_t *snd_seq_fifo_cell_out(fifo_t * f)
{
	snd_seq_event_cell_t *cell;
	unsigned long flags;

	if (f == NULL) {
		snd_printd("oops: snd_seq_fifo_cell_out() called with NULL fifo\n");
		return NULL;
	}

	snd_spin_lock(f, lock, &flags);
	if (f->head != NULL) {
		cell = f->head;
		f->head = cell->ptr_l;

		/* reset tail if this was the last element */
		if (f->tail == cell)
			f->tail = NULL;

		cell->ptr_l = NULL;
		f->cells--;
	} else {
		/* empty queue */
		snd_spin_unlock(f, lock, &flags);
		return NULL;
	}
	snd_spin_unlock(f, lock, &flags);

	return cell;
}


/* return number of events available in fifo */
int snd_seq_fifo_avail(fifo_t * f)
{
	if (f == NULL) {
		snd_printk("oops: snd_seq_fifo_avail() called with NULL fifo\n");
		return 0;
	}
	return f->cells;
}


/* peek at cell at the head of the fifo */
snd_seq_event_cell_t *snd_seq_fifo_cell_peek(fifo_t * f)
{
	if (f == NULL) {
		snd_printk("oops: snd_seq_fifo_avail() called with NULL fifo\n");
		return 0;
	}
	return f->head;
}
