/*
 * Gnophone: A client for the Asterisk PBX
 *
 * Copyright (C) 2000, Linux Support Services, Inc.
 *
 * Written by Mark Spencer
 *
 * Linux/UNIX version distributed under the terms of
 * the GNU General Public License
 *
 * audio-esd.c: ESD Audio Driver
 *
 */
 
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <esd.h>
#include <string.h>
#include <errno.h>
#include "phonecore.h"
#include "audio.h"
#include <iax/frame.h>

static int esd_open(struct audio_channel *c);
static int esd_close__(struct audio_channel *c);
static int esd_activate(struct audio_channel *c);
static int esd_deactivate(struct audio_channel *c);
static int esd_configure(struct audio_channel *c);
static int esd_setspeed(struct audio_channel *c, int speed);
static int esd_sendaudio(struct audio_channel *c, int format, void *data, int len);
static int esd_readaudio(struct audio_channel *c, int format, void *buffer, int *len);

static char esd_driver[] = "Gnophone/ESD";

struct esd_pvt {
	/* We have two file descriptors, one for reading, one for writing */
	int writefd;
};

static int esd_close__(struct audio_channel *c)
{
	struct esd_pvt *pvt = c->pvt;
	esd_close(pvt->writefd);
	esd_close(c->fd);
	c->fd = -1;
	/* Give esd a chance to give up the DSP resource */
	usleep(10000);
	
	return 0;
}


static struct audio_channel *esd_channel_new(int speed)
{
	esd_server_info_t *info;
	struct audio_channel *ac;
	struct esd_pvt *pvt = (struct esd_pvt *)malloc(sizeof(struct esd_pvt));
	int esd = esd_open_sound(NULL);
	ac = audio_new();
	pvt = (struct esd_pvt *)malloc(sizeof(struct esd_pvt));
	bzero(pvt, sizeof(struct esd_pvt));
	if (ac && pvt) {
		if ((info = esd_get_server_info(esd))) {
			snprintf(ac->name, sizeof(ac->name), "Enlightened Sound version %d", 
				info->version);
			esd_free_server_info(info);
		} else 
			strncpy(ac->name, "Enlightened Sound Driver", sizeof(ac->name));
		esd_close(esd);
		ac->priority = 10;
		ac->driver = esd_driver;
		ac->open = esd_open;
		ac->close = esd_close__;
		ac->play_digit = std_play_digit;
		ac->activate = esd_activate;
		ac->ring = std_ring;
		ac->busy = std_busy;
		ac->hz = speed;
		ac->fastbusy = std_fastbusy;
		ac->ringing = std_ringing;
		ac->deactivate = esd_deactivate;
		ac->configure = esd_configure;
		ac->sendaudio = esd_sendaudio;
		ac->readaudio = esd_readaudio;
		/* ac->flush = NULL; */
		ac->setspeed = esd_setspeed;
		ac->sformats = AST_FORMAT_SLINEAR;
		/* ac->cananswer = 0; */
		ac->duplex = 1;			/* ESD is always full duplex */
		/* ac->simduplex = NULL; */
		ac->fd = -1;
		ac->pvt = pvt;
		ac->echocancelled = 0;
		
	} else if (!pvt) { free(ac); ac = NULL; }
	return ac;
}

static int esd_open(struct audio_channel *ac)
{
	struct esd_pvt *pvt = ac->pvt;
	int format = ESD_BITS16 | ESD_MONO | ESD_STREAM | ESD_RECORD | ESD_PLAY;

	if (ac->fd > -1) {
		fprintf(stderr, "Channel %s already open?\n", ac->name);
		return 0;
	}

	ac->fd = esd_record_stream(format, ac->hz, NULL, "gnophone");
	pvt->writefd = esd_play_stream(format, ac->hz, NULL, "gnophone");

	if (ac->fd < 0) {
		fprintf(stderr, "Error opening ESD: %s\n", strerror(errno));
		return -1;
	}
	return 0;
}

static int esd_setspeed(struct audio_channel *ac, int speed)
{
	if (ac->hz != speed) {
		ac->hz = speed;
		esd_close__(ac);
		return esd_open(ac);
	}
	return 0;
}

static int esd_activate(struct audio_channel *ac)
{
	/* Nothing really necessary to activate us */
	return 0;		
}

static int esd_deactivate(struct audio_channel *ac)
{
	/* Nothing really necessary to deactivate us either */
	return 0;
}

static int esd_configure(struct audio_channel *ac)
{
	/* XXX Bug: Need a configuration window, mixer, etc.. XXX */
	return 0;
}

static int esd_readaudio(struct audio_channel *ac, int format, void *data, int *len)
{
	/* Pretty easy with esd */
	int res;
	if (format != AST_FORMAT_SLINEAR) {
		fprintf(stderr, "Can only handle signed linear (little endian) data on %s\n", ac->name);
		return -1;
	}
	res = read(ac->fd, data, *len);
	if (res < 0) {
		fprintf(stderr, "Error reading from %s: %s\n", ac->name, strerror(errno));
		*len = 0;
		return -1;
	}
	*len = res;
	return 0;
}

static int esd_sendaudio(struct audio_channel *ac, int format, void *data, int len)
{
	struct esd_pvt *pvt = ac->pvt;
	if (format != AST_FORMAT_SLINEAR) {
		fprintf(stderr, "Can only handle signed linear (little endian) data on %s\n", ac->name);
		return -1;
	}
	if (write(pvt->writefd, data, len) < len) {
		fprintf(stderr, "%s Error writing frame to fd %d: %s\n", __PRETTY_FUNCTION__,pvt->writefd,strerror(errno));
		return -1;
	}
	return 0;
	
}

char *key() 
{
	return KEY;
}

char *name()
{
	return "esd";
}

int init()
{
	int fd;
	int format = ESD_BITS16 | ESD_MONO | ESD_STREAM | ESD_RECORD | ESD_PLAY;
	struct audio_channel *ac;
	fd = esd_record_stream(format, AUDIO_DEFAULT_SPEED, NULL, "gnophone");
	/* Candidate sound card */
	if (fd > -1) {
		ac = esd_channel_new(AUDIO_DEFAULT_SPEED);
		esd_close(fd);
		if (ac)
			audio_register_channel(ac);
	}
	/* Give esd a chance to give up the DSP resource */
	sleep(1);
	return 0;
}
