

#include <sys/time.h>
#include <sys/socket.h>
#include <sys/file.h>
#include <stdio.h>
#include <signal.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>

#ifdef _SGI_SOURCE
#include <bstring.h>		/* For bzero() declaration */
#endif

#include "sound.h"

#ifndef SA_NOMASK	/* sparc */
#define SA_NOMASK	SA_NODEFER
#endif

#define SND_SERVER	"Maelstrom_sound"

static SoundClient *soundclient=NULL;
static void IO_Handler(int sig)
{
	soundclient->SoundEvent();
}

SoundClient:: SoundClient(int asynchronous)
{
	struct sigaction action;
	int   stream_fds[2];
	long  flags;
	char *argv[3];
	char  fd_buf[24];
	int   i;

	if ( asynchronous == ASYNC_SOUND ) {
		/* Make sure we only have one soundclient per program */
		if ( soundclient != NULL ) {
			fprintf(stderr, 
		"Only one asynchronous SoundClient per program, please.\n");
			exit(255);
		}
		soundclient = this;
		run_async = 1;
	} else
		run_async = 0;

	if ( socketpair(AF_UNIX, SOCK_STREAM, 0, stream_fds) < 0 ) {
		perror("SoundClient: Can't create stream sockets");
		exit(255);
	}
	switch (sndserver_pid=fork()) {
		case -1:	perror("SoundClient: Fork failed");
				exit(255);
		case 0:		goto SndServer;
	}

	/* Initialize the channels */
	for ( i=0; i<NUM_CHANNELS; ++i ) {
		channels[i].in_use = 0;
		channels[i].Callback = NULL;
	}
	Callback_Q.next = NULL;
	Callback_Qtail = &Callback_Q;
	awaiting_reply = 0;

	/* Set ourselves up for asynchronous I/O */
	close(stream_fds[0]);
	if ( run_async ) {
		// Set up the I/O handler (allow interrupts during handler)
		action.sa_handler = IO_Handler;
		sigemptyset(&action.sa_mask);
#ifdef _INCLUDE_HPUX_SOURCE
		action.sa_flags   = 0;
#else
		action.sa_flags   = (SA_NOMASK|SA_RESTART);
#endif
		sigaction(SIGIO, &action, NULL);
		channel_fd = stream_fds[1];
#ifdef _INCLUDE_HPUX_SOURCE
		flags = 1;
		if ( ioctl(channel_fd, FIOASYNC, &flags) < 0 ) {
			perror(
		"SoundClient: Can't set asynchronous I/O on socket");
			exit(255);
		}
		flags = getpid();
		if ( ioctl(channel_fd, SIOCSPGRP, &flags) < 0 ) {
			perror(
		"SoundClient: Can't set process group for socket");
			exit(255);
		}
#else /* linux, SGI, sparc, etc */
		flags = fcntl(channel_fd, F_GETFL, 0);
		flags |= FASYNC;
		if ( fcntl(channel_fd, F_SETFL, flags) < 0 ) {
			perror(
		"SoundClient: Can't set asynchronous I/O on socket");
			exit(255);
		}
		if ( fcntl(channel_fd, F_SETOWN, getpid()) < 0 ) {
			perror(
		"SoundClient: Can't set process group for socket");
			exit(255);
		}
#endif
	}

	/* Check and make sure the server is alive */
	sleep(1);		/* Wait for Sound Server to initialize */
	if ( ! QueryServer() ) {
		fprintf(stderr, "SoundClient: Couldn't contact Sound Server\n");
		exit(255);
	}
	return;

	/* This section of code starts up the sound server */
SndServer:
	close(stream_fds[1]);
	argv[0] = SND_SERVER;
	sprintf(fd_buf, "-s%d", stream_fds[0]);
	argv[1] = fd_buf;
	argv[2] = NULL;
	if ( execvp(argv[0], argv) < 0 ) {
		perror("SoundClient: Can't start sound server");
		exit(255);
	}
}

SoundClient:: ~SoundClient()
{
	char cmdbuf[128];

#ifdef POLITE_CLOSE
	sprintf(cmdbuf, QUIT_FORMAT, QUIT_CMD);
	(void) write(channel_fd, cmdbuf, strlen(cmdbuf)+1);
	close(channel_fd);
	sleep(2);
	/* Let the sound server clean up, then kill it */
	(void) kill(sndserver_pid, SIGTERM);
#else
	close(channel_fd);
#endif
}

void
SoundClient:: SoundEvent(void)
{
	char cmd, *command, cmdbuffer[1024];
	unsigned short channel;
	int len, cmdlen;

#if defined(_SGI_SOURCE) || defined(_INCLUDE_HPUX_SOURCE)  /* Can we talk? */
	fd_set fds; struct timeval tv = { 0, 0 };

	FD_ZERO(&fds); FD_SET(channel_fd, &fds);
#ifdef _INCLUDE_HPUX_SOURCE
	if ( select(channel_fd+1, (int *)&fds, NULL, NULL, &tv) != 1 )
#else
	if ( select(channel_fd+1, &fds, NULL, NULL, &tv) != 1 )
#endif
		return;
#endif /* _SGI_SOURCE || _INCLUDE_HPUX_SOURCE */

	/* Read the event */
readagain:
	if ( (len=read(channel_fd, cmdbuffer, 1024)) < 0 ) {
		if ( errno ) {
			if ( (errno == EINTR) || (errno == EAGAIN) )
				goto readagain;
			perror(
			"SoundClient: Read error while reading from server");
		} // Otherwise, the child process just quit.
#ifdef DEBUG
fprintf(stderr, "bad Sound Event!\n");
#endif
		return;
	}
	++server_events;

#ifdef DEBUG
fprintf(stderr, "Sound Event! (%s, len = %d)\n", cmdbuffer, len);
#endif
	for ( command=cmdbuffer; len>0; ) {
		switch (*command) {
			case OKAY_CMD:		server_reply = 1;
						break;
			case NOKAY_CMD:		server_reply = -1;
						break;
			case CALLBACK_CMD:	sscanf(command, 
							CALLBACK_FORMAT, 
								&cmd, &channel);
						CallBack(channel);
						break;
			default:		fprintf(stderr, 
				"SoundClient: Unknown server command: '%c'\n",
								*command);
						break;
		}
		cmdlen = (strlen(command)+1); 
		command += cmdlen;
		len -= cmdlen;
	}
	return;
}

int
SoundClient:: SendCmdBuf(char *cmdbuf)
{
	server_reply = 0;
	return(write(channel_fd, cmdbuf, strlen(cmdbuf)+1));
}

int
SoundClient:: GetReply(int timeout)
{
	struct timeval tv, *tp;
	fd_set fdset;
	int    retval;

	
	awaiting_reply = 1;
	if ( run_async ) {
		if ( timeout == 0 ) {
			while ( ! server_reply )
				pause();
		} else {
			while ( timeout-- && ! server_reply )
				sleep(1);
		}
	} else {
		while ( ! server_reply ) {
			tv.tv_usec=0;
			tv.tv_sec=timeout;
			if ( timeout )
				tp = &tv;
			else
				tp = NULL;

			FD_ZERO(&fdset);
			FD_SET(channel_fd, &fdset);
#ifdef _INCLUDE_HPUX_SOURCE
			if ( select(64, (int *)&fdset, NULL, NULL, tp) == 0 )
#else
			if ( select(64, &fdset, NULL, NULL, tp) == 0 )
#endif
				break;

			if ( FD_ISSET(channel_fd, &fdset) )
				SoundEvent();
			else 
				perror("SoundClient: select() error");
		}
	}
	retval = ((server_reply == 1) ? 0 : -1);
	DequeueCallbacks();
	return(retval);
}

void
SoundClient:: CallBack(unsigned short channel)
{
	channels[channel].in_use = 0;

	if ( awaiting_reply )
		QueueCallback(channel);
	else {
		if ( channels[channel].Callback )
			channels[channel].Callback(channel);
	}
#ifdef DEBUG
fprintf(stderr, "Channel %hu finished playing!\n", channel);
#endif
}

int
SoundClient:: QueryServer(void)
{
	char cmdbuf[128];

	sprintf(cmdbuf, QUERY_FORMAT, QUERY_CMD);
	if ( SendCmdBuf(cmdbuf) < 0 )
		perror("Can't send query command");
	GetReply(6);
	return(server_reply);
}

int
SoundClient:: SetVolume(unsigned short vol)
{
	char cmdbuf[128];

	sprintf(cmdbuf, VOL_FORMAT, VOL_CMD, vol);
	SendCmdBuf(cmdbuf);
	return(GetReply(0));
}

int
SoundClient:: LoadSound(short sndID)
{
	char cmdbuf[128];

	sprintf(cmdbuf, LOAD_FORMAT, LOAD_CMD, sndID);
	SendCmdBuf(cmdbuf);
	return(GetReply(0));
}

int
SoundClient:: PlaySound(short sndID, short priority, 
					void (*STCallBack)(unsigned short))
{
	unsigned short channel;

	for ( channel=0; channel<NUM_CHANNELS; ++channel) {
		if ( priority > Status(channel) )
			break;
	}
	if ( channel == NUM_CHANNELS )
		return(-1);
	return(PlayChannel(sndID, priority, channel, STCallBack));
}

int
SoundClient:: PlayChannel(short sndID, short priority, 
		unsigned short theChannel, void (*STCallBack)(unsigned short))
{
	char cmdbuf[128];
	int  retval;

	if ( priority <= Status(theChannel) )
		return(-1);

	channels[theChannel].in_use = 1;
	channels[theChannel].ID = sndID;
	channels[theChannel].priority = priority;
	channels[theChannel].Callback = STCallBack;
#ifdef DEBUG
fprintf(stderr, "Playing sound %hd on channel %hu\n", sndID, theChannel);
#endif
	sprintf(cmdbuf, PLAY_FORMAT, PLAY_CMD, sndID, theChannel);
server_events=0;
	SendCmdBuf(cmdbuf);
	if ( GetReply(2) < 0 ) {
if ( ! server_reply )
fprintf(stderr, "No answer from sound server!  (events = %d)\n",server_events);
		return(-1);
	}
	return(theChannel);
}

void
SoundClient:: HaltSounds(void)
{
	unsigned short channel;
	char cmdbuf[128];

	sprintf(cmdbuf, HALTALL_FORMAT, HALTALL_CMD);
	SendCmdBuf(cmdbuf);
	for ( channel=0; channel<NUM_CHANNELS; ++channel )
		channels[channel].in_use = 0;
//fprintf(stderr, "Getting reply...");
	(void) GetReply(0);
}

void
SoundClient:: HaltChannel(unsigned short channel)
{
	char cmdbuf[128];

	sprintf(cmdbuf, HALT_FORMAT, HALT_CMD, channel);
	SendCmdBuf(cmdbuf);
	channels[channel].in_use = 0;
//fprintf(stderr, "Getting reply...");
	(void) GetReply(0);
}


int
SoundClient:: SoundID(unsigned short channel)
{
	if ( channels[channel].in_use )
		return(channels[channel].ID);
	return(-1);
}

int
SoundClient:: IsSoundPlaying(short sndID)
{
	unsigned short channel;

	for ( channel=0; channel<NUM_CHANNELS; ++channel) {
		if ( sndID ) {
			if ( sndID == SoundID(channel) ) {
#ifdef DEBUG
fprintf(stderr, "Sound %d is still playing!\n", SoundID(channel));
#endif
				return(1);
			}
		} else {
			if ( SoundID(channel) != -1 ) {
#ifdef DEBUG
fprintf(stderr, "Sound %d is still playing!\n", SoundID(channel));
#endif
				return(1);
			}
		}
	}
	if ( channel == NUM_CHANNELS )
		return(0);
}


int
SoundClient:: Status(unsigned short channel)
{
	if ( channels[channel].in_use )
		return(channels[channel].priority);
	return(-1);
}

void
SoundClient:: ChannelStatus(unsigned short *chanOnePriority,
			    unsigned short *chanTwoPriority,
			    unsigned short *chanThreePriority,
			    unsigned short *chanFourPriority)
{
	*chanOnePriority = Status(CHANNEL_ONE);
	*chanTwoPriority = Status(CHANNEL_TWO);
	*chanThreePriority = Status(CHANNEL_THREE);
	*chanFourPriority = Status(CHANNEL_FOUR);
}
