/* this file is a part of amp software

   buffer.c: written by Andrew Richards  <A.Richards@phys.canterbury.ac.nz>
   (except printout())

   Last modified by:
   Karl Anders Oygard added flushing of audio buffers, 13 May 1997

 */
#include "amp.h"
#include "transform.h"

#include <sys/types.h>
#ifdef HAVE_MLOCK
#ifdef OS_Linux
#include <sys/mman.h>
#endif
#endif
#ifdef HAVE_SYS_SELECT_H
#include <sys/select.h>
#endif
#include <sys/time.h>
#include <sys/wait.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>

#include "audioIO.h"
#include "audio.h"

#ifndef OS_SunOS
#ifndef OS_FreeBSD
extern int AUSIZ;
#endif
#endif

struct ringBuffer {		/* A ring buffer to store the data in */
	char *bufferPtr;	/* buffer pointer */
	int inPos, outPos;	/* positions for reading and writing */
};


static int buffer_fd;
static int control_fd;

/* little endian systems do not require any byte swapping whatsoever. 
 * big endian systems require byte swapping when writing .wav files, 
 * but not when playing directly
 */

/* This is a separate (but inlined) function to make it easier to implement */
/* a threaded buffer */

inline void audioBufferWrite(char *buf, int bytes)
{
	write(buffer_fd, buf, bytes);
}

void printout(void)
{
	int j;

	if (nch == 2)
		j = 32 * 18 * 2;
	else
		j = 32 * 18;

	if (A_WRITE_TO_FILE) {
#ifndef NO_BYTE_SWAPPING
		short *ptr = (short *) sample_buffer;
		int i = j;

		for (; i >= 0; --i)
			ptr[i] = ptr[i] << 8 | ptr[i] >> 8;
#endif

		fwrite(sample_buffer, sizeof(short), j, out_file);
	}
	if (A_AUDIO_PLAY) {
#ifdef LINUX_REALTIME
		rt_printout((short *) sample_buffer, j * sizeof(short));
#else				/* LINUX_REALTIME */
		if (AUDIO_BUFFER_SIZE == 0)
			audioWrite((char *) sample_buffer, j * sizeof(short));
		else
			audioBufferWrite((char *) sample_buffer, j * sizeof(short));
#endif				/* LINUX_REALTIME */
	}
}

int AUDIO_BUFFER_SIZE;

#define bufferSize(A) (((A)->inPos+AUDIO_BUFFER_SIZE-(A)->outPos) % AUDIO_BUFFER_SIZE)
#define bufferFree(A) (AUDIO_BUFFER_SIZE-1-bufferSize(A))

void initBuffer(struct ringBuffer *buffer)
{
	buffer->bufferPtr = malloc(AUDIO_BUFFER_SIZE);
	if (buffer->bufferPtr == NULL) {
		die("Unable to allocate write buffer\n");
	}
#ifdef HAVE_MLOCK
	mlock(buffer->bufferPtr, AUDIO_BUFFER_SIZE);
#endif
	buffer->inPos = 0;
	buffer->outPos = 0;
	DB(buffer2, msg("Allocated %d bytes for audio buffer\n", AUDIO_BUFFER_SIZE));
}

void freeBuffer(struct ringBuffer *buffer)
{
#ifdef HAVE_MLOCK
	munlock(buffer->bufferPtr, AUDIO_BUFFER_SIZE);
#endif
	free(buffer->bufferPtr);
}

/* This just sends some bogus data on the control pipe, which will cause */
/* the reader to flush its buffer. */

void audioBufferFlush()
{
	if (AUDIO_BUFFER_SIZE != 0) {
		int dummy;

		/* We could use the control pipe for passing commands to the */
		/* audio player process, but so far we haven't bothered. */

		write(control_fd, &dummy, sizeof dummy);
	} else
		audioFlush();
}

/* The decoded data are stored in a the ring buffer until they can be sent */
/* to the audio device. Variables are named in relation to the buffer */
/* ie writes are writes from the codec to the buffer and reads are reads */
/* from the buffer to the audio device */

int audioBufferOpen(int frequency, int stereo, int volume)
{
	struct ringBuffer audioBuffer;

	int inFd, outFd, ctlFd, cnt, cntr = 0, pid;
	int inputFinished = FALSE;
	int percentFull;
	fd_set inFdSet, outFdSet;
	fd_set *outFdPtr;
	struct timeval timeout;
	int filedes[2];
	int controldes[2];

	DB(buffer, msg("Starting audio buffer\n"));

	if (pipe(filedes) || pipe(controldes)) {
		perror("pipe");
		exit(-1);
	}
	DB(buffer, msg("readpipe=%d writepipe=%d\n", filedes[0], filedes[1]));
	if ((pid = fork()) != 0) {	/* if we are the parent */
		control_fd = controldes[1];
		close(filedes[0]);
		buffer_fd = filedes[1];
		close(controldes[0]);
		return (pid);	/* return the pid */
	}
	DB(buffer, msg("Audio buffer (pipe) starting\n"));

	/* we are the child */
	close(filedes[1]);
	inFd = filedes[0];
	close(controldes[1]);
	ctlFd = controldes[0];
	audioOpen(frequency, stereo, volume);
	outFd = getAudioFd();

	initBuffer(&audioBuffer);

	while (1) {
		timeout.tv_sec = 0;
		timeout.tv_usec = 0;
		FD_ZERO(&inFdSet);
		FD_ZERO(&outFdSet);
		FD_SET(ctlFd, &inFdSet);
		FD_SET(outFd, &outFdSet);

		DB(buffer,
		   msg("BS=%d inPos=%d outPos=%d bufferSize=%d bufferFree=%d\n",
		AUDIO_BUFFER_SIZE, audioBuffer.inPos, audioBuffer.outPos,
		    bufferSize(&audioBuffer), bufferFree(&audioBuffer)));

		if (bufferSize(&audioBuffer) < AUSIZ) {		/* is the buffer too empty */
			outFdPtr = NULL;	/* yes, don't try to write */
			if (inputFinished)	/* no more input, buffer exhausted -> exit */
				break;
		} else
			outFdPtr = &outFdSet;	/* no, select on write */

		/* check we have at least AUSIZ bytes left (don't want <1k bits) */
		if ((bufferFree(&audioBuffer) >= AUSIZ) && !inputFinished)
			FD_SET(inFd, &inFdSet);

/* The following selects() are basically all that is left of the system
   dependent code outside the audioIO_*.c files. These selects really
   need to be moved into the audioIO_*.c files and replaced with a
   function like audioIOReady(inFd, &checkIn, &checkAudio, wait) where
   it checks the status of the input or audio output if checkIn or
   checkAudio are set and returns with checkIn or checkAudio set to TRUE
   or FALSE depending on whether or not data is available. If wait is
   FALSE the function should return immediately, if wait is TRUE the
   process should BLOCK until the required condition is met. NB: The
   process MUST relinquish the CPU during this check or it will gobble
   up all the available CPU which sort of defeats the purpose of the
   buffer.

   This is tricky for people who don't have file descriptors (and
   select) to do the job. In that case a buffer implemented using
   threads should work. The way things are set up now a threaded version
   shouldn't be to hard to implement. When I get some time... */

		/* check if we can read or write */
		if (select(MAX3(inFd, outFd, ctlFd) + 1, &inFdSet, outFdPtr, NULL, NULL) > -1) {
			if (outFdPtr && FD_ISSET(outFd, outFdPtr)) {	/* need to write */
				int bytesToEnd = AUDIO_BUFFER_SIZE - audioBuffer.outPos;

				percentFull = 100 * bufferSize(&audioBuffer) / AUDIO_BUFFER_SIZE;
#if defined(DEBUG)
				if ((cntr++ % (16 / (AUSIZ / 4096))) == 0)
					msg("\rBuffer (%2d%%) %6d", percentFull, bufferSize(&audioBuffer));
#endif
				if (AUSIZ > bytesToEnd) {
					cnt = audioWrite(audioBuffer.bufferPtr + audioBuffer.outPos, bytesToEnd);
					cnt += audioWrite(audioBuffer.bufferPtr, AUSIZ - bytesToEnd);
					audioBuffer.outPos = AUSIZ - bytesToEnd;
				} else {
					cnt = audioWrite(audioBuffer.bufferPtr + audioBuffer.outPos, AUSIZ);
					audioBuffer.outPos += AUSIZ;
				}

				DB(buffer2, msg("buffer: wrote %d bytes (to audio)\n", cnt));
			}
			if (FD_ISSET(inFd, &inFdSet)) {		/* need to read */
				cnt = read(inFd, audioBuffer.bufferPtr + audioBuffer.inPos, MIN(AUSIZ, AUDIO_BUFFER_SIZE - audioBuffer.inPos));
				if (cnt >= 0) {
					audioBuffer.inPos = (audioBuffer.inPos + cnt) % AUDIO_BUFFER_SIZE;

					DB(buffer2, msg("buffer: read %d bytes\n", cnt));

					if (cnt == 0) {
						inputFinished = TRUE;
						DB(buffer, msg("inputFinished\n"));
					}
				} else {
					perror("read");
					exit(-1);
				}
			}
			if (FD_ISSET(ctlFd, &inFdSet)) {
				int dummy;

				cnt = read(ctlFd, &dummy, sizeof dummy);
				if (cnt >= 0) {
					audioBuffer.inPos = audioBuffer.outPos = 0;
					audioFlush();
				} else {
					perror("read");
					exit(-1);
				}
			}
		} else {
			perror("select");
			exit(-1);
		}
	}
	close(inFd);
	DB(buffer, msg("Buffer pipe closed %d\n", inFd);
	    )
	    audioClose();
	_exit(0);
	return 0;		/* just to get rid of warnings */
}

void audioBufferClose()
{
	/* Close the buffer pipe and wait for the buffer process to close */
	close(buffer_fd);
	waitpid(0, 0, 0);
}
