/*
 * rplayd.c
 *
 * Copyright (c) 1992 by Mark Boyns
 *
 * Permission to use, copy, modify, and distribute this software and its
 * documentation for any purpose and without fee is hereby granted, provided
 * that the above copyright notice appear in all copies and that both that
 * copyright notice and this permission notice appear in supporting
 * documentation.  This software is provided "as is" without express or
 * implied warranty.
 */

#include <sys/types.h>
#include <sys/time.h>
#include <sys/file.h>
#include <sys/param.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/socket.h>
#include <sys/signal.h>
#include <sys/fcntl.h>
#include <lwp/lwp.h>
#include <lwp/stackdep.h>
#include <netinet/in.h>
#include <stdio.h>
#include <search.h>
#include <strings.h>
#include "libst.h"
#include "rplay.h"

/*
 * The defines below can be used to customize rplayd.
 */
#define MAX_SOUNDS		1024	/* maximum sounds that rplayd can handle */
					/* this must be more than the sounds in rplay.conf */
#define SPOOL_SIZE		8	/* number of sounds that can be played at once */
#define SOUND_LIST_SIZE		32	/* maximum size of a sound list */
#define AUDIO_BUFSIZE		500	/* rplayd audio buffer size */
#define TIME_SLICE		50000	/* micro seconds */
#define RPLAYD_TIMEOUT		6000 	/* 50000usec = 1/20sec, 20*5*60 = 5 mins */
#define AUDIO_CLOSE_TIMEOUT	100	/* close audio device when not in use */
					/* 50000usec = 1/20sec, 20*5 = 5 secs */

/*
 * These should not be changed
 */
#define MINPRIO			1
#define MAXPRIO			10
#define SUN_AUDIO_DEVICE	"/dev/audio"
#define SUN_MAGIC		0x2e736e64
#define SUN_HDRSIZE		24

typedef struct sound {
	char	*name;			/* name of the sound file */
	char	path[MAXPATHLEN];	/* pathname of the sound file */
	char	*buf;			/* the sound buffer */
	char	*start;			/* where to start */
	char	*stop;			/* where to stop */
 	int	size;			/* sizeof the sound buffer */
} Sound;

typedef struct spool {
	int	nsounds;		/* nsounds in the sound list */
	int	curr;			/* current playing sound in the list */
	int	state;			/* state of the current sound */
	Sound	*slist[SOUND_LIST_SIZE];/* the sound list */
	int	vlist[SOUND_LIST_SIZE];	/* the volume list */
	char	*ptr;			/* sound buffer pointer */
	char	*end;			/* the end of the sound buffer */
} Spool;

Spool	spool[SPOOL_SIZE];		/* the sound spool */
Sound	*list[SOUND_LIST_SIZE]; 	/* temporary list used by the server */
char	recvbuf[MAX_PACKET];		/* buffer for incoming packets */

ENTRY	*hsearch();
Sound	*lookup();
int	scheduler(), player(), server();
int	in = 0, out = 0;
int	audio_fd = -1;

main()
{
	config();
	spool_init();

	pod_setmaxpri(MAXPRIO);
	lwp_setstkcache(4096, 4);
	lwp_create((thread_t *)0, scheduler, MAXPRIO, 0, lwp_newstk(), 0);
	lwp_create((thread_t *)0, player, MINPRIO, 0, lwp_newstk(), 0);
	lwp_create((thread_t *)0, server, MINPRIO, 0, lwp_newstk(), 0);

	exit(0);
}

config()
{
	Sound	*s;
	ENTRY	e;
	FILE	*fp;
	char	buf[MAXPATHLEN], *p;

	fp = fopen(RPLAY_CONF, "r");
	if (fp == NULL) {
		fprintf(stderr, "rplayd: cannot open %s\n", RPLAY_CONF);
		exit(1);
	}

	if (hcreate(MAX_SOUNDS) == 0) {
		fprintf(stderr, "rplayd: cannot create hash table\n");
		exit(1);
	}

	while(fgets(buf, sizeof(buf), fp) != NULL) {
		if (buf[0] == '#') {
			continue;
		}

		s = (Sound *)malloc(sizeof(Sound));
		sscanf(buf, "%s", s->path);
		p = rindex(s->path, '/');
		s->name = p == NULL ? s->path : p+1;
		s->buf = NULL;
		s->size = 0;

		e.key = s->name;
		e.data = (char *)s;
		if (hsearch(e, ENTER) == NULL) {
			fprintf(stderr, "rplayd: the hash table is full, too many sounds\n");
			exit(1);
		}
	}

	fclose(fp);
}

spool_init()
{
	int	i;

	for(i = 0; i < SPOOL_SIZE; i++) { 
		spool_clear(i);
	}
}

Sound	*lookup(name)
char	*name;
{
	ENTRY		e, *found;
	Sound		*s;

	e.key = name;
	found = hsearch(e, FIND);
	if (found == NULL) {
		return NULL;
	}

	s = (Sound *)found->data;

	if (s->buf == NULL) {
		struct stat	st;
		long		magic, hdr_size;
		int		fd;

		fd = open(s->path, O_RDONLY, 0);
		if (fd < 0) {
			return NULL;
		}

		if (fstat(fd, &st) < 0) {
			return NULL;
		}

		s->size = st.st_size;

		/*
		 * use mmap to load the sound
		 */
		s->buf = mmap(0, s->size, PROT_READ, MAP_SHARED, fd, 0);
		if (s->buf == (caddr_t)-1) {
			return NULL;
		}

		/*
		 * make sure it is a Sun audio file
		 */
		magic = *((long *)s->buf);
		if (magic != SUN_MAGIC) {
			return NULL;
		}

		/*
		 * ignore the header
		 */
		hdr_size = *((long *)s->buf + 1);
		if (hdr_size < SUN_HDRSIZE) {
			return NULL;
		}

		s->start = s->buf + hdr_size;
		s->stop = s->buf + s->size - 1;

		close(fd);
	}

	return s;
}

server()
{
	int			sock_fd, x, restarted, len;
	struct sockaddr_in	s, f;
	int			flen = sizeof(f);
	char			*p;
	int			attr, i, n, j;   
	int			found;
	Sound			*sound;

#ifdef INETD
	sock_fd = 0;
#else
	bzero(&s, sizeof(s));
	s.sin_family = AF_INET;
	s.sin_port = htons(RPLAY_PORT);
	s.sin_addr.s_addr = INADDR_ANY;

	sock_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sock_fd < 0) {
		perror("socket");
		pod_exit(1);
	}

	if (bind(sock_fd, &s, sizeof(s)) < 0) {
		perror("bind");
		pod_exit(1);
	}
#endif INETD

	for(;;) {
		len = recvfrom(sock_fd, recvbuf, sizeof(recvbuf), 0, &f, &flen);
		p = recvbuf;
		attr = *p++;
		switch(attr) {
		case RPLAY_PLAY:
			for(i = 0; i < SPOOL_SIZE; i++) {
				if (spool[i].state == RPLAY_NULL) {
					break;
				}	      
			} 
			if (i == SPOOL_SIZE) {
				break;
			}
			do {
				sound = lookup(p);  
				if (sound == NULL) {
					spool_clear(i);
					break;
				}
				p += strlen(p) + 1; 
				spool[i].slist[spool[i].nsounds] = sound;   
				spool[i].vlist[spool[i].nsounds] = (unsigned char)*p++;
				spool[i].nsounds++;
			} while(*p != NULL);
			if (sound != NULL) {
				spool[i].ptr = spool[i].slist[0]->start;
				spool[i].end = spool[i].slist[0]->stop;
				spool[i].curr = 0;
				spool[i].state = attr;   
				in++;
			}
			break;
 
		case RPLAY_STOP: 
		case RPLAY_PAUSE:
		case RPLAY_CONTINUE:
			n = 0;  
			do {
				list[n] = lookup(p);   
				if (list[n] == NULL) {
					break;
				} 
				n++;
				p += strlen(p) + 1; 
			} while(*p != NULL);
			if (list[n-1] == NULL) {
				break;
			}
			for(i = 0; i < SPOOL_SIZE; i++) {
				if (spool[i].nsounds == n) {
					found = 1;
					for(j = 0; j < n; j++) { 
				 		if (list[j] != spool[i].slist[j]) {
							found = 0;		 
							break;
						} 
					} 
					if (found) {
						spool[i].state = attr;
						break;
					} 
				}
			}
			break;

		default:
			fprintf(stderr, "rplayd: unknown rplay attribute (0x%0x)\n", attr);  
			break;
		}
	}
}
 
scheduler()
{
	struct timeval	t;
	int		cnt;

	cnt = 0;
	t.tv_sec = 0;
	t.tv_usec = TIME_SLICE;

	for(;;) {
		if (in == out) {
			cnt++;
			switch(cnt) {
			case RPLAYD_TIMEOUT:
				pod_exit(0);

			case AUDIO_CLOSE_TIMEOUT:
				if (audio_fd != -1) {
					close(audio_fd);
					audio_fd = -1;
				}
				break;
			}
		}
		else {
			cnt = 0;
		}
		lwp_sleep(&t);
		lwp_resched(MINPRIO);
	}
}

player()
{
	int	i, index, empty;
	long	total; 
	long	val;
	char	buf[AUDIO_BUFSIZE];

	index = 0;

	for(;;) {
		total = 0; 
		empty = 1;
		for(i = 0; i < SPOOL_SIZE; i++) { 
			switch(spool[i].state) { 
			case RPLAY_CONTINUE: 
				spool[i].state = RPLAY_PLAY;
			case RPLAY_PLAY:
				empty = 0;
				val = st_ulaw_to_linear((unsigned char)*spool[i].ptr) * spool[i].vlist[spool[i].curr];  
				total += val >> 7;
				if (spool[i].ptr == spool[i].end) {
					spool[i].curr++;
					if (spool[i].curr == spool[i].nsounds) {  
						spool_clear(i);
						out++; 
					}  
					else {  
						spool[i].ptr = spool[i].slist[spool[i].curr]->start; 
						spool[i].end = spool[i].slist[spool[i].curr]->stop; 
					}
				} 
				else {
					spool[i].ptr++;
				}
				break; 
			
			case RPLAY_STOP:
				spool_clear(i);
				out++;
				break;
				
			default:
				break;
			}
		}

		if (empty) {
			if (index > 0) {
				if (audio_fd == -1) {
					audio_fd = open(SUN_AUDIO_DEVICE, O_WRONLY | O_NDELAY, 0);
				}
				if (audio_fd != -1) {
					write(audio_fd, buf, index);
				}
				index = 0;
			}
			lwp_yield(SELF);
		}
		else {
			buf[index++] = st_linear_to_ulaw(total);
			if (index == AUDIO_BUFSIZE) {
				if (audio_fd == -1) {
					audio_fd = open(SUN_AUDIO_DEVICE, O_WRONLY | O_NDELAY, 0);
				}
				if (audio_fd != -1) {
					write(audio_fd, buf, index);
				}
				index = 0;
			}
		}
	}
}
 
spool_clear(i)
int	i;
{
	spool[i].curr = 0;
	spool[i].nsounds = 0;
	spool[i].ptr = NULL;
	spool[i].end = NULL;
	spool[i].state = RPLAY_NULL;
}
