#include <asterisk/file.h>

#define FRAME_SIZE 160

static char *app = "ALSAMonitor";

static char *synopsis = "Monitor ALSA channel console";
static char *descrip = 
"  ALSAMonitor(password): Allows the user to monitor the ALSA console\n"
"bi directionally.  Monitoring is performed regardless of the state of\n"
"any call that might be on the channel.\n";

STANDARD_LOCAL_USER;

LOCAL_USER_DECL;


static int alsa_monitor_rpipe[2];
static int alsa_monitor_wpipe[2];
static int alsa_monitor_readflag = 0;
static int alsa_monitor_writeflag = 0;

static void alsa_monitor_read(char *buf, int len)
{
	int err = 0;
	int offset = len;
	if (!alsa_monitor_readflag) {
		return;
	}

	if (!buf || !len) {
		return;
	}

	do {
		err = write(alsa_monitor_rpipe[1], buf, offset);
#if 0
		printf("Wrote %d bytes\n", err);
#endif		
		/* Pipe might be full */
		if (!err)
			return;
		if (err < 0) {
			if (errno != EAGAIN)
				ast_log(LOG_ERROR, "Problem writing monitor pipe: %s\n", strerror(errno));
			return;
		}
		offset -= err;
		buf += err;
	} while (offset);

	return;
}

static void alsa_monitor_write(char *buf, int len)
{
	int err = 0;
	int offset = len;
	if (!alsa_monitor_writeflag) {
		return;
	}

	if (!buf || !len) {
		return;
	}

	do {
		/* Pipe might be full */
		err = write(alsa_monitor_wpipe[1], buf, offset);
		if (!err)
			return;
		if (err < 0) {
			if (errno != EAGAIN)
				ast_log(LOG_ERROR, "Problem writing monitor write pipe: %s\n", strerror(errno));
			return;
		}
		offset -= err;
		buf += err;
	} while (offset);

	return;
}

static int alsa_monitor_exec(struct ast_channel *chan, void *data)
{
	short __readbuf[160 + AST_FRIENDLY_OFFSET/2];
	short writebuf[160];
	short *readbuf;
	struct ast_frame *f, fw;
	char *passwd;
	struct localuser *u;
	struct ast_channel *c;
	int res = 0, amt, wamt, sum;
	int fd;
	int ms = -1;
	int x;
	

	if (data)
		passwd = data;
	else
		passwd = "";
	
	LOCAL_USER_ADD(u);
	
	alsa_monitor_readflag = 1;
	alsa_monitor_writeflag = 1;


	if (chan->state != AST_STATE_UP)
		ast_answer(chan);

	ast_set_write_format(chan, AST_FORMAT_SLINEAR);

	readbuf = __readbuf + AST_FRIENDLY_OFFSET/2;

	for (;;) {
		fd = -1;
		ms = -1;
		c = ast_waitfor_nandfds(&chan, 1, &alsa_monitor_rpipe[0], 1, NULL, &fd, &ms);
		if (c) {
			/* Channel has something */
			f = ast_read(c);
			if (!f) {
				res = -1;
				break;
			}
			ast_frfree(f);
		} else if (fd > -1) {
			/* Read from the read pipe */
			amt = read(alsa_monitor_rpipe[0], readbuf, 320);
			if (amt < 0) {
				ast_log(LOG_ERROR, "Problem reading monitor read pipe: %s\n", strerror(errno));
				res = -1;
				break;
			}
			/* Try to read as much from the write pipe */
			wamt = read(alsa_monitor_wpipe[0], writebuf, amt);
			if (wamt < 0) {
				if (errno != EAGAIN) {
					ast_log(LOG_ERROR, "Problem reading monitor write pipe: %s\n", strerror(errno));
					res = -1;
					break;
				} else
					wamt = 0;
			}

			for (x=0;x<wamt/2;x++) {
				/* Add together whatever we can.  */
				sum = readbuf[x] + writebuf[x];
				if (sum > 32767)
					sum = 32767;
				if (sum < -32768)
					sum = -32768;
				readbuf[x] = sum;
			}
			memset(&fw, 0, sizeof(fw));
			fw.frametype = AST_FRAME_VOICE;
			fw.subclass = AST_FORMAT_SLINEAR;
			fw.timelen = amt / 16;
			fw.datalen = amt;
			fw.data = readbuf;
			fw.src = "ALSAMonitor";
			fw.mallocd = 0;
			if (ast_write(chan, &fw)) {
				ast_log(LOG_WARNING, "Write failed: %s\n", strerror(errno));
				res = -1;
				break;
			}
		}

	}

	LOCAL_USER_REMOVE(u);	

	alsa_monitor_readflag = 0;
	alsa_monitor_writeflag = 0;
	
	ast_log(LOG_DEBUG, "Monitor disconnected\n");

	return res;
}

static int alsa_monitor_start(void)
{
	int err;
	int flags;
	int i = 0;

	err = pipe(alsa_monitor_rpipe);
	if (err) {
		ast_log(LOG_ERROR, "Error starting rpipe: %s\n", strerror(errno));
		return -1;
	}
	err = pipe(alsa_monitor_wpipe);
	if (err) {
		ast_log(LOG_ERROR, "Error starting wpipe: %s\n", strerror(errno));
		return -1;
	}

	for (i = 0; i < 2; i++) {
		flags = fcntl(alsa_monitor_rpipe[i], F_GETFL);
		fcntl(alsa_monitor_rpipe[i], F_SETFL, flags | O_NONBLOCK);
		flags = fcntl(alsa_monitor_wpipe[i], F_GETFL);
		fcntl(alsa_monitor_wpipe[i], F_SETFL, flags | O_NONBLOCK);
	}

	err = ast_register_application(app, alsa_monitor_exec, synopsis, descrip);
	if (err < 0) {
		ast_log(LOG_ERROR, "Error registering alsa_monitor with asterisk\n");
		return err;
	}
	return 0;
}

