/*
 * Copyright 2001 Niels Provos <provos@citi.umich.edu>
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. All advertising materials mentioning features or use of this software
 *    must display the following acknowledgement:
 *      This product includes software developed by Niels Provos.
 * 4. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/param.h>

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif

#include <sys/ioctl.h>
#include <sys/time.h>
#include <sys/queue.h>

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
#include <event.h>

#include "buffer.h"
#include "voip.h"

struct vbuff* buffer_get(int);

#ifndef MIN
#define	MIN(a,b)	(((a)<(b))?(a):(b))
#endif /* !MIN */

extern int debug;
static int tailq_init = 0;
static int entries = 0;

#define BUFGET(pn,bid) struct vbuff pn = buffer_get(bid)

TAILQ_HEAD(tailhead, vbuff) qh;
struct vbuff {
	int used;
	int maxtalks;
	int maxsamples;
	int maxbuffer;

    /* Buffer information */
	int *idinuse;
	bufid *id;
	int32_t *dataseqnr;
	u_int16_t *dataid;
	
	int *snap_idinuse;
	bufid *snap_id;
	int32_t *snap_dataseqnr;
	u_int16_t *snap_dataid;

    /* Buffered data for each channel */
	int16_t **data;
	int *dataoff; 

    /* Reassemble for voice/video playback */
	int16_t *buf; //[MAXBUFFER];
	int bufentries;
	int buf_fd;

	struct event bufev; /* XXX */

	TAILQ_ENTRY(vbuff) next;
};   

/*
 * creates a new buffer struct and inserts it into the list 
 */
bh
buffer_new(int maxtalks, int maxsamples, int maxbuffer)
{
	struct vbuff *vbp;
	int i;

	if (!tailq_init) {
		TAILQ_INIT(&qh);
		tailq_init = 1;
	}

	vbp = (struct vbuff*)malloc(sizeof(struct vbuff));
	vbp->used = 1;
	vbp->maxtalks = maxtalks;
	vbp->maxsamples = maxsamples;
	vbp->maxbuffer = maxbuffer;		
#define balloc(type, var, num) vbp->var = (type*)malloc(sizeof(type)*num)
	balloc(int, idinuse, maxtalks);
	balloc(bufid, id, maxtalks);
	balloc(int32_t, dataseqnr, maxtalks);
	balloc(u_int16_t, dataid, maxtalks);	

	balloc(int, snap_idinuse, maxtalks);
	balloc(bufid, snap_id, maxtalks);
	balloc(int32_t, snap_dataseqnr, maxtalks);
	balloc(u_int16_t, snap_dataid, maxtalks);

	balloc(int, dataoff, maxtalks);
	balloc(int16_t, buf, maxbuffer);
#undef balloc
	vbp->data = (int16_t**)malloc(sizeof(int16_t*)*maxtalks);
	for (i = 0; i < maxtalks; i++)
		vbp->data[i] = (int16_t*)malloc(sizeof(int16_t)*maxsamples);
	TAILQ_INSERT_TAIL(&qh, vbp, next);
	return (bh)entries++;
}

/*
 * deletes buffers
 */
void
buffer_delete(void)
{
	struct vbuff *vptr;
	int i;

 	for (vptr = TAILQ_FIRST(&qh); vptr; vptr = TAILQ_FIRST(&qh)) {
		TAILQ_REMOVE(&qh, vptr, next);
		free(vptr->idinuse);
		free(vptr->id);		
		free(vptr->dataseqnr);
		free(vptr->dataid);
		free(vptr->snap_idinuse);
		free(vptr->snap_id);
		free(vptr->snap_dataseqnr);
		free(vptr->snap_dataid);
		free(vptr->dataoff);
		free(vptr->buf);
		for (i = 0; i < vptr->maxtalks; i++)
			free(vptr->data[i]);
		free(vptr->data); 
		free(vptr);
	}
}

/*
 * deprecates a buffer: unsets used flag
 */
void
buffer_deprecate(bh bhd)
{
	BUFGET(*b, bhd);

	b->used = 0;
}

/*
 * returns a struct vbuff pointer from a list index (handler)
 */
struct vbuff*
buffer_get(bh bhd)
{
	int i = 0;
	struct vbuff *vptr;
	
	TAILQ_FOREACH(vptr, &qh, next) 
		if ((i++ == (bh)bhd) && vptr->used) return vptr;
	errx(1, "bad buffer handler passed, giving up");
	return NULL;
}

void
buffer_cb(int fd, short event, void *arg)
{
	bh bhd = *(bh*)arg;
	BUFGET(*b, bhd);
	
	buffer_empty(bhd, 0);

	if (buffer_output(bhd))
		event_add(&b->bufev, NULL);
}

void
buffer_init(bh bhd)
{
	BUFGET(*b, bhd);
	bh *bhdp;

	b->buf_fd = fileno(stdout);
	if (fcntl(b->buf_fd, F_SETFL, O_NONBLOCK) == -1)
		err(1, "fcntl failed");

	bhdp = (bh*)malloc(sizeof(bh));
	*bhdp = bhd;
	event_set(&b->bufev, b->buf_fd, EV_WRITE, buffer_cb, (void*)bhdp);

	buffer_clear(bhd);
}

void
buffer_clear(int bhd)
{
	int i;
	BUFGET(*b, bhd);

	memset(b->idinuse, 0, sizeof(int) * b->maxtalks);
	memset(b->dataoff, 0, sizeof(int) * b->maxtalks);

	for (i = 0; i < b->maxtalks; i++)
		memset(b->data[i], 0, sizeof(int16_t) * b->maxsamples);
	
	b->bufentries = 0;

	event_del(&b->bufev);
}

/*
 * tries to write bufentries*sizeof(int16_t) bytes to buf_fd.  if
 * write is failed or misaligned on int16_t, it reports an error.  it
 * also removes any data succesfully written from the buffer,
 * "shifting" other buffer data in to its position.  it returns the
 * number of remaining buffer entries not outputted
 */
int
buffer_output(bh bhd)
{
	BUFGET(*b, bhd);
	int clen;

	if (!b->bufentries)
		return (0);

	/* Try to output data */
	clen = write(b->buf_fd, b->buf, b->bufentries * sizeof(int16_t));
	
	if (clen == -1) {
		fprintf(stderr, "write(%d, %p, %d) -> %d\n",
			b->buf_fd, b->buf, b->bufentries * sizeof(int16_t), clen);

		if (errno == EAGAIN || errno == EINTR)
			return (1);
		err(1, "write failed");
	}

	if (clen % sizeof(int16_t))
		errx(1, "non-aligned write");
	clen /= sizeof(int16_t);
		
	memmove(b->buf, &b->buf[clen], (b->bufentries - clen) * sizeof(int16_t));
	b->bufentries -= clen;

	return (b->bufentries);
}


/*
 * empty buffer from data to buf for voice playback.  up to
 * len*sizeof(int16_t) bytes
 */
int
buffer_empty(bh bhd, int len)
{
	BUFGET(*b, bhd);
	int i, j, max = b->maxsamples, clen;

	for (i = 0; i < b->maxtalks; i++) {
		clen = b->dataoff[i];
		if (clen < len)
			clen = len;
		if (max > clen)
			max = clen;
	}
	
	/* Nothing to be emptied */
	if (max == 0)
		return (0);

	if (b->bufentries + max > b->maxbuffer)
		errx(1, "buffers full");

	memset(&b->buf[b->bufentries], 0, max * sizeof (int16_t));
	for (i = 0; i < b->maxtalks; i++) {
		clen = MIN(b->dataoff[i], max);
		for (j = 0; j < clen; j++)
			b->buf[b->bufentries + j] += b->data[i][j];
		b->dataoff[i] -= clen;
		memmove(b->data[i], b->data[i] + clen, b->dataoff[i] * sizeof(int16_t));
	}
	b->bufentries += max;

	return (1);
}

/*
 * creates new space for the bufid in internal arrays if the bufid
 * doesn't already exists.  enqueues len samples onto buffer.  calls
 * buffer_empty() if it overflows (followed by a buffer_output
 * callback).
 */
int
buffer_enqueue(bh bhd, bufid *sid, u_int16_t seqid, int32_t seqnr,
			   int16_t *samples, int len)
{
	BUFGET(*b, bhd);
	int offset, doff;

	for (offset = 0; offset < b->maxtalks; offset++) {
		if (b->idinuse[offset] &&
			memcmp(sid, &b->id[offset], sizeof(bufid)) == 0)
			break;
	}

	if (offset == b->maxtalks) {
		for (offset = 0; offset < b->maxtalks; offset++)
			if (!b->idinuse[offset])
				break;
		if (offset == b->maxtalks) {
			warnx("could not allocate buffer");
			return (-1);
		}

		b->idinuse[offset] = 1;
		b->dataseqnr[offset] = seqnr;
		b->dataid[offset] = seqid;
		memcpy(&b->id[offset], sid, sizeof(bufid));
	}

	if (seqnr - b->dataseqnr[offset] > 0) {
		if (debug)
			fprintf(stderr, "WARNING: lost %u bytes to %u\n",
					seqnr - b->dataseqnr[offset], seqnr);
	} else if (seqnr - b->dataseqnr[offset] < 0) {
		if ((debug >= 2) &&
			(seqnr - b->dataseqnr[offset] < -len)
#ifdef HAVE_CENSOR
		    && !voip_doinginsert()
#endif
		    )
			fprintf(stderr, "WARNING: reordered: %u < %u\n",
					seqnr, b->dataseqnr[offset]);
		return (0);
	}
	b->dataseqnr[offset] = seqnr + len;
	b->dataid[offset] = seqid + 1;

if (b->dataoff[offset] + len > b->maxsamples) {
		buffer_empty(bhd, len);
		event_add(&b->bufev, NULL);
	}

	doff = b->dataoff[offset];
	memcpy(b->data[offset] + doff * sizeof(int16_t),
		   samples, len * sizeof(int16_t));
	
	b->dataoff[offset] += len;
        
	if (buffer_empty(bhd, 0))
		event_add(&b->bufev, NULL);

	return (0);
}

/* Take a snapshot of active connection */
void
buffer_snapshot(int bid)
{
	BUFGET(*b, bid);
	int i, j;

	memset(b->snap_idinuse, 0, sizeof (b->snap_idinuse));
	for (j = 0, i = 0; i < b->maxtalks; i++) {
		if (!b->idinuse[i])
			continue;

		b->snap_idinuse[j] = 1;
		memcpy(&b->snap_id[j], &b->id[i], sizeof(bufid));
		b->snap_dataseqnr[j] = b->dataseqnr[i];
		b->snap_dataid[j] = b->dataid[i];

		j++;
	}
}

void
buffer_snap_nextframe(bh bhd, int advance)
{
	BUFGET(*b, bhd);
	int i;

	for (i = 0; i < b->maxtalks; i++) {
		if (!b->snap_idinuse[i])
			break;

		b->snap_dataseqnr[i] += advance * FRAMELEN;
		b->snap_dataid[i] += advance;
	}
}

int
buffer_snap_info(bh bhd, int which, bufid **id, u_int16_t *dataid,
				 u_int32_t *seqnr)
{
	BUFGET(*b, bhd);

	if (which >= b->maxtalks || !b->snap_idinuse[which])
		return (-1);

	*id = &b->snap_id[which];
	*dataid = b->snap_dataid[which];
	*seqnr = b->snap_dataseqnr[which];

	return (0);
}


syntax highlighted by Code2HTML, v. 0.9.1