/*
 * librplay.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/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <varargs.h>
#include <malloc.h>
#include <stdio.h>
#include "version.h"
#include "rplay.h"

int 	rplay_errno;

char	*rplay_errlist[] = {
	"no error",			/* RPLAY_ERROR_NONE */
	"out of memory",		/* RPLAY_ERROR_MEMORY */
	"host not found",		/* RPLAY_ERROR_HOST */
	"rplay service not found",	/* RPLAY_ERROR_SERVICE */
	"cannot connect to socket",	/* RPLAY_ERROR_CONNECT */
	"cannot create socket",		/* RPLAY_ERROR_SOCKET */
	"unknown rplay attribute",	/* RPLAY_ERROR_ATTRIBUTE */
	"error writing to socket",	/* RPLAY_ERROR_WRITE */
	"error closing socket",		/* RPLAY_ERROR_CLOSE */ 
	"append error",			/* RPLAY_ERROR_APPEND */
	"max packet size exceeded",	/* RPLAY_ERROR_PACKET_SIZE */
	"cannot enable broadcast",	/* RPLAY_ERROR_BROADCAST */
};

/*
 * macro used to gradually increase an rplay buffer by COPY_SIZE bytes
 */
#define COPY_SIZE	128
#define COPY(rp, ptr, n) \
	rp->grow = 0; \
	while(rp->len + n > rp->size) { \
		rp->grow = 1; \
		rp->size += COPY_SIZE; \
		if (rp->size > MAX_PACKET) { \
			rplay_errno = RPLAY_ERROR_PACKET_SIZE; \
			return -1; \
		} \
	} \
	if (rp->grow) { \
		rp->buf = realloc(rp->buf, rp->size); \
		if (rp->buf == NULL) { \
			rplay_errno =  RPLAY_ERROR_MEMORY; \
			return -1; \
		} \
	} \
	bcopy(ptr, rp->buf+rp->len, n); \
	rp->len += n; \

/*
 * create an RPLAY instance
 */
RPLAY	*rplay_create()
{
	RPLAY	*rp;

	rplay_errno = RPLAY_ERROR_NONE;

	rp = (RPLAY *)malloc(sizeof(RPLAY));
	if (rp == NULL) {
		rplay_errno =  RPLAY_ERROR_MEMORY;
		return NULL;
	}
	else {
		rp->len = 0; 
		rp->buf = (char *)malloc(COPY_SIZE);
		if (rp->buf == NULL) {
			rplay_errno =  RPLAY_ERROR_MEMORY;
			return NULL;
		}
		rp->size = COPY_SIZE;
		return rp;
	}
}
 
/*
 * free up memory used by rp
 */
int	rplay_destroy(rp) 
RPLAY	*rp;
{ 
	free(rp->buf);
	free(rp);	
} 

/*
 * set rplay attributes
 */
int	rplay_set(va_alist)
va_dcl
{
	va_list		args;
	RPLAY		*rp;
	char		*p;
	unsigned char	val, *valp = &val;
	int		attr, append;

	rplay_errno = RPLAY_ERROR_NONE;

	va_start(args);
	rp = va_arg(args, RPLAY *);
	attr = va_arg(args, int); 

	append = 0;
	switch(attr) {
	case RPLAY_APPEND_PLAY:
		if (rp->len <= 0 || rp->buf[0] != RPLAY_PLAY) {
			rplay_errno = RPLAY_ERROR_APPEND;
			return -1;
		}
		attr = RPLAY_PLAY;
		append = 1;
		break;

	case RPLAY_APPEND_PAUSE:
		if (rp->len <= 0 || rp->buf[0] != RPLAY_PAUSE) {
			rplay_errno = RPLAY_ERROR_APPEND;
			return -1;
		}
		attr = RPLAY_PAUSE;
		append = 1;
		break;

	case RPLAY_APPEND_STOP: 
		if (rp->len <= 0 || rp->buf[0] != RPLAY_STOP) {
			rplay_errno = RPLAY_ERROR_APPEND;
			return -1;
		}
		attr = RPLAY_STOP;
		append = 1;
		break;

	case RPLAY_APPEND_CONTINUE:
		if (rp->len <= 0 || rp->buf[0] != RPLAY_CONTINUE) {
			rplay_errno = RPLAY_ERROR_APPEND;
			return -1;
		}
		attr = RPLAY_CONTINUE;
		append = 1;
		break;

	case RPLAY_APPEND_VOLUME_PLAY:
		if (rp->len <= 0 || rp->buf[0] != RPLAY_PLAY) {
			rplay_errno = RPLAY_ERROR_APPEND;
			return -1;
		}
		attr = RPLAY_VOLUME_PLAY;
		append = 1;
		break;
	}

	if (append) {
		rp->len--;
	}
	else {
		rp->len = 0;
	}

	switch(attr) {  
	case RPLAY_PLAY:
	case RPLAY_PAUSE:
	case RPLAY_STOP: 
	case RPLAY_CONTINUE:
	case RPLAY_VOLUME_PLAY:
		if (!append) {
			*valp = attr == RPLAY_VOLUME_PLAY ? RPLAY_PLAY : attr;
			COPY(rp, valp, 1);
		}
		for(;;) {
			p = va_arg(args, char *);
			if (p == NULL) {
				*valp = NULL;
				COPY(rp, valp, 1);
				break;
			}
			COPY(rp, p, strlen(p)+1);
			if (attr == RPLAY_VOLUME_PLAY) {
				*valp = (unsigned char)va_arg(args, int);
				COPY(rp, valp, 1);
			} 
			else if (attr == RPLAY_PLAY) {
				*valp = RPLAY_DEFAULT_VOLUME;
				COPY(rp, valp, 1);
			}
		}
		break; 
		 
	default:
		rplay_errno = RPLAY_ERROR_ATTRIBUTE;
		return -1;
	} 

	va_end(args);

	return 0;
}

/*
 * create a UDP socket for the given host
 */
int	rplay_open(host)
char	*host;
{
	struct hostent		*hp;
#ifdef INETD
	struct servent		*sp;
#endif INETD
	int			rplay_fd;
	u_long			addr;
	struct sockaddr_in	s;
	int			on = 1;

	rplay_errno = RPLAY_ERROR_NONE;

#ifdef INETD
	sp = getservbyname("rplay", "udp");
	if (sp == NULL) {
		rplay_errno = RPLAY_ERROR_SERVICE;
		return -1;
	}
#endif INETD

	bzero(&s, sizeof(s));

	addr = inet_addr(host);
	if (addr == 0xffffffff) {
		hp = gethostbyname(host);
		if (hp == NULL) {
			rplay_errno = RPLAY_ERROR_HOST;
			return -1;
		}
		bcopy(hp->h_addr, &s.sin_addr.s_addr, hp->h_length);
	}
	else {
		bcopy(&addr, &s.sin_addr.s_addr, sizeof(addr));
	}

#ifdef INETD
	s.sin_port = sp->s_port;
#else
	s.sin_port = htons(RPLAY_PORT);
#endif INETD
	s.sin_family = AF_INET;

	rplay_fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (rplay_fd < 0) {
		rplay_errno = RPLAY_ERROR_SOCKET;
		return -1;
	}

	if (connect(rplay_fd, &s, sizeof(s)) < 0) {
		rplay_errno = RPLAY_ERROR_CONNECT;
		return -1;
	}

	/*
	 * enable broadcasting
	 */
	if (setsockopt(rplay_fd, SOL_SOCKET, SO_BROADCAST, &on, sizeof(on)) < 0) {
		rplay_errno = RPLAY_ERROR_BROADCAST;
		return -1;
	}

	return rplay_fd;
}

/*
 * write the RPLAY packet to the given socket descriptor
 */
int	rplay(rplay_fd, rp)
int	rplay_fd;
RPLAY	*rp;
{
	rplay_errno = RPLAY_ERROR_NONE;

	if (write(rplay_fd, rp->buf, rp->len) != rp->len) {
		rplay_errno = RPLAY_ERROR_WRITE;
		return -1;
	}

	return 0;
}

/*
 * close the given rplay socket descriptor
 */
int	rplay_close(rplay_fd)
int	rplay_fd;
{
	rplay_errno = RPLAY_ERROR_NONE;

	if (close(rplay_fd) < 0) {
		rplay_errno = RPLAY_ERROR_CLOSE;
		return -1;
	}

	return 0;
}

/*
 * report an rplay error to stderr
 */
rplay_perror(s)
char	*s;
{
	fprintf(stderr, "%s: %s\n", s, rplay_errlist[rplay_errno]);
}
