/*
 * FILE:    vss_client.c
 * PROGRAM: Example of a simple standalone client for VSS 3.1,
 *          without vssClient.h or libsnd.a.
 *
 * PLATFORMS:
 *          Irix 6.5 (MIPSpro 7.30 cc)
 *          Red Hat 6.1 (Linux 2.2.13, egcs-2.91.66)
 *          Windows 2000 (cygwin Beta 20.1, gcc 2.95.22)
 * AUTHOR:  Camille Goudeseune, NCSA
 * DATE:    10 July 2000
 */

#include <arpa/inet.h>
#include <errno.h>
#include <fcntl.h>
#include <netdb.h>
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

/*********************** TYPEDEFS AND PROTOTYPES ***********************/

#define hNil (-1.f)
typedef void *OBJ;

#define cchmm 5120 
typedef struct mm
	{
	char fRetval;
	char rgch[cchmm];
	} mm;

void	actorMessage(char* messagename);
void	actorMessageF(char* messagename, float f);
void	actorMessageFF(char* messagename, float f1, float f2);
void	actorMessageFD(char* messagename, float f, int d);
void	actorMessageFDD(char* messagename, float f, int d1, int d2);
void	actorMessageFS(char* messagename, float f, const char* sz);
float	actorMessageRetval(char* messagename);
float	actorGetReply(void);
float	createActor(const char* actorType); 
void	deleteActor(const float actorHandle); 
float	beginSound(const float hactor);

void	MsgsendObj(OBJ obj, struct sockaddr_in *paddr, mm* pmm);
OBJ	BgnMsgsend(char *szHostname, int channel);
int	BeginSoundServer(void);
int	BeginSoundServerAt(char * szHostname);
int	SelectSoundServer(int serverHandle);
void	EndSoundServer(void);
int	PingSoundServer(void);
void	Msgsend(struct sockaddr_in *paddr, mm* pmm);
void	clientMessageCall(char* Message);

int	FMsgrcv(void);
float	HnoteFromAckNote(void);
const	char* SzFromDataReply(void);


/*********************** COMMUNICATION WITH VSS ***********************/

/*
 * This section is an example of connecting to VSS.  Its details
 * can be changed to match particular applications.
 */

static float hactorMG = hNil;
static float hactorBeep = hNil;
static float hsoundBeep = hNil;
char szCmd[300]; /* buffer for constructing VSS commands */

int ConnectToVSS(char* szHostname)
{
    if (!BeginSoundServerAt(szHostname))
        {
		// Failed to find a running copy of VSS.
        return 0;
        }

	/* Connection established.  Set up VSS with a few actors. */

    actorMessage("LoadDSO msgGroup.so");
    actorMessage("LoadDSO fm.so");
    actorMessage("SetPrintCommands 2"); /* verbose debugging spew */

    hactorBeep = createActor("FmActor");
    if (hactorBeep == hNil)
        {
        fprintf(stderr, "Error: VSS failed to create FmActor.\n");
        EndSoundServer();
        return 0;
        }

    hactorMG = createActor("MessageGroup");
    if (hactorMG == hNil)
        {
        fprintf(stderr, "Error: VSS failed to create SocketActor.\n");
        actorMessageF("Delete %g", hactorBeep);
        EndSoundServer();
        return 0;
        }

    sprintf(szCmd, "BeginSoundPaused %g SetAmp .3 SetModIndex 4", hactorBeep);
    hsoundBeep = actorMessageRetval(szCmd);

   // sprintf(szCmd, "AddMessage %g SetModIndex %f *0 .4",
    sprintf(szCmd, "AddMessage %g SetCarFeedback %f *0 .4",
		hactorMG, hsoundBeep);
	actorMessage(szCmd);

	/* So other VSS clients can get at the actor handle. */
	actorMessageF("StoreValue 0 %g", hsoundBeep);
	actorMessageF("StoreValue 1 %g", hactorMG);

	return 1;
}

void DisconnectFromVSS(void)
{
    actorMessageF("Active %g 0", hsoundBeep);
    actorMessageF("Active %g 0", hactorBeep);
    actorMessageF("Active %g 0", hactorMG);

    actorMessageF("Delete %g", hsoundBeep);
    actorMessageF("Delete %g", hactorBeep);
    actorMessageF("Delete %g", hactorMG);

    EndSoundServer();
}


/******************** END OF COMMUNICATION WITH VSS ********************/

/*
 * This section connects to VSS, sends it some messages, and disconnects.
 */

int main(int argc, char** argv)
{
	if (argc != 2)
		{
		printf("usage: %s hostname\n", *argv);
		return -1;
		}

	if (!ConnectToVSS(argv[1]))
		return -1;

	{
	float freq, buzz;

	/* Unpause the sound. */
	actorMessageF("SetPause %g 0", hsoundBeep);

	/* Play a scale of 12 semitones. */
	for (freq = 660.; freq > 330.; freq /= 1.0594631)
		{
		actorMessageFF("SetFreq %g %g", hsoundBeep, freq);
		usleep(100000); /* pause for 0.1 seconds */
		}

	/* Change the timbre, and store it for other vss clients to get to. */
	for (buzz = 0.; buzz < .9; buzz += 0.1)
		{
		actorMessageFF("SendData %g [%g]", hactorMG, buzz);
		actorMessageF("StoreValue 42 %g", buzz);
		usleep(100000);
		}

	usleep(500000);
	}

	DisconnectFromVSS();
}


/*********************** VSS CLIENT LIBRARY ***********************/

/*
 *	This section is a subset of the source code of libsnd.a.
 *	Most VSS clients links with libsnd.a, instead of
 *	including all this code directly.  The advantage of libsnd.a
 *	is that it parses .aud files while this shorter code doesn't.
 *
 *	Don't change code in this section unless you really know what
 *	you're doing.
 */

static char szT[100];	/* for accumulating message strings */

/* These are shortcut C functions, wrappers for common ways to call actorMessage(). */

/* Create an actor on the server.  returns a handle. */
float createActor(const char* actorType)
{
	sprintf(szT, "Create %s", actorType);
	return actorMessageRetval(szT);
}

void deleteActor(const float actorHandle)
{
	sprintf(szT, "Delete %f", actorHandle);
	actorMessage(szT);
}

/****************** Lower level stuff ********************/

float beginSound(const float hactor)
{
	char sz[100];
	sprintf(sz, "BeginSound %f", hactor);
	return actorMessageRetval(sz);
}

float actorGetReply(void)
{
	if(!FMsgrcv())
	{
		fprintf(stderr, "vss client error: timed out waiting for server\n");
		return -1.0;
	}
	return HnoteFromAckNote();
}

void actorMessage(char* messagename)
{
/*** Send the message to the server ***/
	static mm mmT;
	if (strlen(messagename) > 1498)
		{
		fprintf(stderr, "vss client error: actorMessage() argument too long.\n");
		return;
		}
	strcpy(mmT.rgch, messagename);
	mmT.fRetval = 0;
	Msgsend(NULL, &mmT);
}

void actorMessageF(char* messagename, float f)
{
	char sz[1000];
	sprintf(sz, messagename, f);
	actorMessage(sz);
}

void actorMessageFF(char* messagename, float f1, float f2)
{
	char sz[1000];
	sprintf(sz, messagename, f1, f2);
	actorMessage(sz);
}

void actorMessageFD(char* messagename, float f, int d)
{
	char sz[1000];
	sprintf(sz, messagename, f, d);
	actorMessage(sz);
}

void actorMessageFDD(char* messagename, float f, int d1, int d2)
{
	char sz[1000];
	sprintf(sz, messagename, f, d1, d2);
	actorMessage(sz);
}

void actorMessageFS(char* messagename, float f, const char* sz)
{
	char szCmd[1000];
	sprintf(szCmd, messagename, f, sz);
	actorMessage(szCmd);
}

float actorMessageRetval(char* messagename)
{
/*** Send the message to the server ***/
	static mm mmT;
	if (strlen(messagename) > 1498)
		{
		fprintf(stderr, "vss client error: actorMessageRetval() argument too long.\n");
		return -1.0;
		}
	strcpy(mmT.rgch, messagename);
	mmT.fRetval = 1;
	Msgsend(NULL, &mmT);

	return actorGetReply();
}

static OBJ* vpobj;
#define	MAX_NUM_SERVERS	10
static OBJ vobjArray[MAX_NUM_SERVERS];
static int numServers = 0;
int	currentServerHandle = 0;
static mm mmT;

typedef struct
{
	struct sockaddr_in addr;
	int len;
	int sockfd;
	int channel;
} desc;

#define fFalse 0
#define fTrue 1

OBJ BgnMsgsend(char *szHostname, int channel)
{
	struct sockaddr_in  cl_addr;
	int sockfd;
	desc *o = (desc *)malloc(sizeof(desc));

	if (!o)
		return NULL;

	o->channel = channel;
	memset((char *)&o->addr, 0, sizeof(o->addr));
	o->addr.sin_family = AF_INET;
	o->addr.sin_addr.s_addr = inet_addr(szHostname);
	o->addr.sin_port = htons(channel);
	if ((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) >= 0)
		{
		memset((char *)&cl_addr, 0, sizeof(cl_addr));
		cl_addr.sin_family = AF_INET;
		cl_addr.sin_addr.s_addr = htonl(INADDR_ANY);
		cl_addr.sin_port = htons(0);
		if (bind(sockfd, (struct sockaddr *)&cl_addr,
			sizeof(cl_addr)) < 0)
			{
			perror("can't bind");
			close(sockfd);
			sockfd = -1;
			}
		}
	else
		printf("unable to make socket\n");

	/* Non-blocking socket. */
#ifdef WIN32
	{
	u_long arg = 1;
	ioctlsocket(sockfd, FIONBIO, &arg);
	}
#else
	if (fcntl(sockfd, F_SETFL, FNDELAY) < 0)
		{
		perror("warning: couldn't make socket nonblocking.\n");
		}
	/* setsockopt(sockfd, F_SETFL, FNDELAY); */ /* sometimes useful too */
#endif
#ifdef FIONBIO
	{
	u_long arg = 1;
	ioctl(sockfd, FIONBIO, &arg);
	}
#endif
	o->sockfd = sockfd;
	o->len = sizeof(o->addr);
	return (OBJ)o;
}

/***************************************/

static void EndMsgsend(OBJ obj)
{
	close(((desc*)obj)->sockfd);
}

/***************************************/

static int sendudp(struct sockaddr_in *sp, int sockfd, long count, void  *b)
{
	return sendto(sockfd, (const char*)b, (int)count, 0,
		(const struct sockaddr*)sp, sizeof(*sp)) == count;
}

/***************************************/

/* This doesn't waste cpu, it just moves some of the parsing
   from the server side to the client side. */

static void VSS_StripZerosInPlace(char* sz)
{
	char szT[sizeof(mm) + 5];
	char* pchSrc = sz;
	char* pchDst = szT;

	for ( ; ; /* *pchDst=='.' or ' '*/ *pchDst++ = *pchSrc++)
		{
		while (*pchSrc && *pchSrc != '.')
			*pchDst++ = *pchSrc++;

		if (!*pchSrc) /* end of string */
			break;

		/* We've reached a decimal point. */

		/* If it's NOT preceded by a space or a digit,
		 * don't abbreviate as it's probably NOT a floating-point number.
		 */
		if (!(pchSrc[-1] == ' ' || (pchSrc[-1]>='0' && pchSrc[-1]<='9')))
			continue;

		if (!strncmp(pchSrc, ".000000 ", 8))
			{
			pchSrc += 7;
			/* now *pchSrc == ' ' */
			continue;
			}
		if (!strcmp(pchSrc, ".000000"))
			/* end of string, just discard it. */
			break;
		}

	/* Copy back into original string. */
	*pchDst = '\0';
	strcpy(sz, szT);
}

void MsgsendObj(OBJ obj, struct sockaddr_in *paddr, mm* pmm)
{
	desc* o = (desc *)obj;
	if (!obj)
		return;

	/* Strip trailing zeros for shorter more legible messages. */
	/* in vi:  s/\.[0]* / /g */

	VSS_StripZerosInPlace(pmm->rgch);

	if (paddr == NULL)
		paddr = &o->addr;
	if(!sendudp(paddr, o->sockfd, (long)strlen(pmm->rgch)+1+1, pmm))
		/* the second +1 is for the fRetval field. */
		perror("VSS client error: MsgsendObj send failed");
}

void Msgsend(struct sockaddr_in *paddr, mm* pmm)
{
	if (vpobj != NULL)
		MsgsendObj(*vpobj, paddr, pmm);
}

static struct timeval vtimeout = { 2L, 500000L };

static int FMsgrcvCore(OBJ pv)
{
	desc *o;
	struct sockaddr_in  cl_addr;
	int clilen;
	int r;
	fd_set read_fds;
	struct timeval timeout = { 2L, 500000L };

#	define MAXMESG 32768
	static char rgchT[MAXMESG];
	if (vpobj == NULL && pv == NULL)
		return fFalse;

	o = (desc *)(pv ? pv : *vpobj);
	FD_ZERO(&read_fds);
	FD_SET(o->sockfd, &read_fds);            
	/* Reassign the struct before every select() call, the manpage advises. */
	timeout.tv_sec = vtimeout.tv_sec;
	timeout.tv_usec = vtimeout.tv_usec;

	r = select(o->sockfd+1, &read_fds, (fd_set *)0, (fd_set *)0, &timeout);

	if (r <= 0)
		return fFalse;

	if (FD_ISSET(o->sockfd, &read_fds))
		{
		int fGotSomething = 0;
		int n;
		while (clilen = sizeof(cl_addr),
			(n=recvfrom(o->sockfd, rgchT, MAXMESG, 0,
			(struct sockaddr*)&cl_addr, &clilen)) >0)
			{
			fGotSomething = 1;
			if (!pv)
				/* really a 3.0 client, not a 2.3 client */
				clientMessageCall(rgchT+1/*strip off fRetval with +1*/);
			}
		if (!fGotSomething && n < 0)
			{
			// ECONNREFUSED is when a Ping finds no VSS running.
			if (errno != ECONNREFUSED && errno != EWOULDBLOCK)
				perror("VSS client: internal error, FMsgrcv recvfrom() failed");

			return fFalse;
			}
		return fTrue;
		}
	/* This should not happen */
	fprintf(stderr, "VSS client: internal error 4\n");
	return fFalse;
}

int FMsgrcv(void)
{
	return FMsgrcvCore(NULL);
}

/* set up the UDP connection.  szHostname defaults to localhost. */
static int FInitUdp(OBJ* pobj, char* szHostname, int wChannel)
{
	if (isalpha(szHostname[0]))
		{
		char ipAddr[16];
		char **cp;
		struct hostent * myHostent;

		if (!(myHostent = gethostbyname(szHostname)))
			{
			fprintf(stderr, "VSS client error: host name \"%s\" doesn't exist?\n",
				szHostname);
			return fFalse;
			}

		cp = myHostent->h_addr_list;
		sprintf(ipAddr, "%s", inet_ntoa(*(struct in_addr *)(*cp)));
		szHostname = ipAddr;
		}
	*pobj = BgnMsgsend(szHostname, wChannel);
	return *pobj != 0;
}

static float vhnote = hNil;
static char vszDataReply[cchmm] = {0};
static void AckNote(float returnVal)
{
	/* stuff it in this global */
	vhnote = returnVal;
}

static void AckDataReply(char* returnVal)
{
	/* stuff it in this global */
	strncpy(vszDataReply, returnVal, cchmm-1);
	vszDataReply[cchmm-1] = '\0';
}

float HnoteFromAckNote(void)
{
	return vhnote;
}

const char* SzFromDataReply(void)
{
	return vszDataReply;
}

/* Send a Ping msg without args. */
int PingSoundServer(void)
{
	sprintf(mmT.rgch, "Ping");
	Msgsend(NULL, &mmT);
	return FMsgrcv();
}

void EndSoundServer(void)
{
	if (!vpobj)
		{
		fprintf(stderr, "VSS client error: Sound server not attached!\n\t(Did you call EndSoundServer() more than once?)\n");
		return;
		}

	EndMsgsend(*vpobj);
	*vpobj = 0;
	vpobj = NULL;
	currentServerHandle = 0; /* NULL handle in this case */
}

int wSendChannel = 7999;

static int BeginSoundServerCore(char* szHostname)
{
	if (!szHostname || !*szHostname)
		{
		char *szT = getenv("SOUNDSERVER");
		szHostname = (szT == NULL ) ? "127.0.0.1" : szT;
		}

	if (numServers >= MAX_NUM_SERVERS)
		{
		fprintf(stderr, "vss client error: Too many servers running to open another on %s.\n",
			szHostname ? szHostname : "this machine");
		return fFalse;
		}

	vpobj = NULL;
	if (!FInitUdp(vobjArray + numServers, szHostname, wSendChannel))
		goto LAbort;

	vpobj = vobjArray + numServers;
	if (!PingSoundServer())
		{
LAbort:
		printf("VSS client error: couldn't find vss running");
		if (szHostname)
			printf(" on %s", szHostname);
		printf("\n");
		return fFalse;
		}

	numServers++;
	currentServerHandle = numServers;
	return numServers;     /* return a server handle */
}

int BeginSoundServerAt(char * szHostname)
{
	return BeginSoundServerCore(szHostname);
}

int BeginSoundServer(void)
{
	return BeginSoundServerCore(NULL);
}

int SelectSoundServer(int serverHandle)
{
	if (serverHandle > numServers)	return fFalse;
	if (serverHandle < 1)		return fFalse;
	/* handles range from 1 to MAX_NUM_SERVERS */

	vpobj = vobjArray + serverHandle - 1;
	currentServerHandle = serverHandle;
	return(fTrue);
}

/*************************************/

void clientMessageCall(char* Message)
{
	if (!strncmp(Message, "AckNoteMsg ", 11))
		{
		float z = 0.;
		if (1 != sscanf(Message+11, "%f", &z))
			{
			fprintf(stderr, "vss client error: bad args to AckNoteMsg (%s)", Message);
			return;
			}
		AckNote(z);
		}

	else if (!strncmp(Message, "DataReply ", 10))
		{
#if 0
		// This test isn't needed, because lack of args is ok here, it's general-purpose.
		#define cchMax 2000
		char sz[cchMax];
		if (1 != sscanf(Message+10, "%[^]", sz))
			{
			fprintf(stderr, "vss client error: bad args to DataReply (string missing?) (%s)", Message);
			return;
			}
#endif
		AckDataReply(Message+10);
		}

	else
		fprintf(stderr, "vss client error: unrecognized message \"%s\" passed to clientMessageCall()\n", Message);
}
