#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <fcntl.h>
#include <endian.h>
#include <sys/soundcard.h>
#include <sys/ioctl.h>

#include "snd.h"
#include "prefs.h"
#include "misc.h"

#undef SND_INFO_DEBUG

/* ---------------------------------------------------------------------- */

#define	SOUND_BUF_LEN	16384
#define	SOUND_VOL	0.5

static int sound_fd = -1;

static int16_t snd_16_buffer[SOUND_BUF_LEN];
static u_int8_t snd_8_buffer[SOUND_BUF_LEN];

static char snd_error_str[1024];

/* ---------------------------------------------------------------------- */

static void snderr(const char *fmt, ...)
{
	va_list args;

	va_start(args, fmt);
	vsnprintf(snd_error_str, sizeof(snd_error_str), fmt, args);
	snd_error_str[sizeof(snd_error_str) - 1] = 0;
	va_end(args);
#if 1
	fprintf(stderr, "%s\n", snd_error_str);
#endif
}

/* ---------------------------------------------------------------------- */

static int opensnd(int direction, int samplerate)
{
#ifdef SND_INFO_DEBUG
	audio_buf_info info;
#endif
	unsigned int sndparam, wanted;
	int fd;

	if ((fd = open(prefs.sounddev, direction)) < 0) {
		snderr("open: %s: %m", prefs.sounddev);
		return -1;
	}

	if (prefs.eightbitsound)
		wanted = AFMT_U8;	/* 8 bit unsigned */
	else
		wanted = AFMT_S16_NE;	/* 16 bit signed, native byteorder */

	sndparam = wanted;
	if (ioctl(fd, SNDCTL_DSP_SETFMT, &sndparam) < 0) {
		snderr("ioctl: SNDCTL_DSP_SETFMT: %m");
		goto error;
	}
	if (sndparam != wanted) {
		snderr("Requested sample format not supported");
		goto error;
	}

	sndparam = 0;			/* mono */
	if (ioctl(fd, SNDCTL_DSP_STEREO, &sndparam) < 0) {
		snderr("ioctl: SNDCTL_DSP_STEREO: %m");
		goto error;
	}
	if (sndparam != 0) {
		snderr("Cannot set mono audio\n");
		goto error;
	}

	sndparam = samplerate;
	if (ioctl(fd, SNDCTL_DSP_SPEED, &sndparam) < 0) {
		snderr("ioctl: SNDCTL_DSP_SPEED: %m");
		goto error;
	}
	if (sndparam != samplerate) {
		snderr("Warning: Sampling rate is %u, requested %u",
			sndparam,
			samplerate);
	}

	/* Request a buffer size of 512 samples */
	if (prefs.eightbitsound)
		sndparam = 0x00000009;
	else
		sndparam = 0x0000000A;

	/* Unlimited amount of buffers for RX, four for TX */
	if (direction == O_RDONLY)
		sndparam |= 0x7FFF0000;
	else
		sndparam |= 0x00040000;

	if (ioctl(fd, SNDCTL_DSP_SETFRAGMENT, &sndparam) < 0) {
		snderr("ioctl: SNDCTL_DSP_SETFRAGMENT: %m");
		goto error;
	}

#ifdef SND_INFO_DEBUG
	if (direction == O_RDONLY) {
		if (ioctl(fd, SNDCTL_DSP_GETISPACE, &info) < 0) {
			snderr("ioctl: SNDCTL_DSP_GETISPACE: %m");
			goto error;
		}
	} else {
		if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &info) < 0) {
			snderr("ioctl: SNDCTL_DSP_GETOSPACE: %m");
			goto error;
		}
	}

	fprintf(stderr, "Audio buffer size: %d bytes, number of buffers: %d\n",
			info.fragsize,
			info.fragstotal);
#endif

#if 0
	/* non-blocking io if requested */
	if (nonblock && ioctl(fd, SNDCTL_DSP_NONBLOCK, 0) < 0) {
		snderr("ioctl: SNDCTL_DSP_NONBLOCK: %m");
		goto error;
	}
#endif

	return fd;

error:
	close(fd);
	return -1;
}

/* ---------------------------------------------------------------------- */

int sound_open_for_write(int srate)
{
	/* use stdout for sound output */
	if (prefs.testmode == TRUE) {
		sound_fd = 1;
		return 1;
	}

	if (prefs.fullduplexsound == TRUE) {
		if (sound_fd < 0)
			sound_fd = opensnd(O_RDWR, srate);

		return sound_fd;
	}

	sound_fd = opensnd(O_WRONLY, srate);

	return sound_fd;
}

int sound_open_for_read(int srate)
{
	/* use stdin for sound input */
	if (prefs.testmode == TRUE) {
		sound_fd = 0;
		return 0;
	}

	if (prefs.fullduplexsound == TRUE) {
		if (sound_fd < 0)
			sound_fd = opensnd(O_RDWR, srate);

		return sound_fd;
	}

	sound_fd = opensnd(O_RDONLY, srate);

	return sound_fd;
}

void sound_close(void)
{
	if (prefs.testmode == TRUE) {
		sound_fd = -1;
		return;
	}

	if (prefs.fullduplexsound == TRUE)
		return;

	/* never close stdin/out/err */
	if (sound_fd > 2) {
		if (ioctl(sound_fd, SNDCTL_DSP_SYNC, 0) < 0)
			snderr("ioctl: SNDCTL_DSP_SYNC: %m");
		close(sound_fd);
		sound_fd = -1;
	}
}

char *sound_error(void)
{
	return snd_error_str;
}

/* ---------------------------------------------------------------------- */

int sound_write(float *buf, int count)
{
	void *p;
	int i;

	if (sound_fd < 0) {
		snderr("sound_write: fd < 0");
		return -1;
	}

	if (count > SOUND_BUF_LEN) {
		snderr("sound_write: count > SOUND_BUF_LEN");
		return -1;
	}

	if (prefs.eightbitsound) {
		for (i = 0; i < count; i++)
			snd_8_buffer[i] = (buf[i] * 127.0 * SOUND_VOL) + 128.0;

		count *= sizeof(u_int8_t);
		p = snd_8_buffer;
	} else {
		for (i = 0; i < count; i++)
			snd_16_buffer[i] = buf[i] * 32767.0 * SOUND_VOL;

		count *= sizeof(int16_t);
		p = snd_16_buffer;
	}

	if ((i = write(sound_fd, p, count)) < 0)
		snderr("sound_write: write: %m");

	return i;
}

int sound_read(float *buf, int count)
{
	int len, i;

	if (sound_fd < 0) {
		snderr("sound_read: fd < 0");
		return -1;
	}

	if (count > SOUND_BUF_LEN) {
		snderr("sound_read: count > SOUND_BUF_LEN");
		return -1;
	}

	if (prefs.eightbitsound) {
		count *= sizeof(u_int8_t);

		if ((len = read(sound_fd, snd_8_buffer, count)) < 0)
			goto error;

		len /= sizeof(u_int8_t);

		for (i = 0; i < len; i++)
			buf[i] = (snd_8_buffer[i] - 128) / 128.0;
	} else {
		count *= sizeof(int16_t);

		if ((len = read(sound_fd, snd_16_buffer, count)) < 0)
			goto error;

		len /= sizeof(int16_t);

		for (i = 0; i < len; i++)
			buf[i] = snd_16_buffer[i] / 32768.0;
	}

	return len;

error:
	if (errno != EAGAIN)
		snderr("sound_read: read: %m");

	return len;
}

/* ---------------------------------------------------------------------- */

