/* interface library for Lehenbauer SMUS Server -- Aztec C version */

/* Copyright 1989 Karl Lehenbauer, All Rights Reserved
 *
 * This code was made available under license to a customer of Hackercorp.
 * It is considered a trade secret of Hackercorp and the customer is expected
 * to take reasonable measures to insure the security of this code.
 *
 * Hackercorp
 * 3918 Panorama
 * Missouri City, TX  77459
 * (713) 438-4964
 * Usenet: uunet!sugar!karl   
 * Internet/BITNET: karl@sugar.hackercorp.com
 *
 */

#include <exec/types.h>
#include <exec/memory.h>
#include <exec/nodes.h>
#include <exec/lists.h>
#include <functions.h>
#include <devices/audio.h>
#include <workbench/workbench.h>
#include <workbench/startup.h>
#include <fcntl.h>
#include <stdio.h>

#include "smus_requests.h"

extern short panic_in_progress;

char *server_died = "one of the smus server programs died";

/*
 * This file contains the routines a program needs to include (and call)
 * in order to load and play songs with the Lehenbauer SMUS Server
 *
 * There is a message-based interface and a RPC (Remote Procedure Call)
 * interface.  Most people find the RPC interface easier to understand,
 * although in some circumstances the message-based interface may provide
 * certain advantages.  
 * 
 */

static struct PlayerRequest player_request;

static struct PlayerRequest loader_request;

/* we find the player and loader request ports or launch the
 * loader and player
 */
static struct MsgPort *player_request_port = NULL;

static struct MsgPort *loader_request_port = NULL;

/* we get replies to out requests through the reply request port
 * and launch replies through a launch reply port 
 */

static struct MsgPort *reply_request_port = NULL;

static struct MsgPort *launch_reply_port = NULL;

static struct WBStartup *player_wbstartup = NULL, *loader_wbstartup = NULL;

#define YES 1
#define NO 0

static short launched_player = NO;
static short launched_loader = NO;

/* where the player and loader reside */
static char player_name[] = "ksmu:playsmus";
static char *player_launch_argv[] = {player_name, NULL};

static char loader_name[] = "ksmu:loadsmus";
static char *loader_launch_argv[] = {loader_name, NULL};

/* the SMUS equivalent of Unix errno, numbers are in smusrequest.h,
 * conversion to text included in this file 
 */
int es_errno;

static short terminate_in_progress = NO;


/* ------------ workbench launch ---------------------------- */

/* The workbench launch and free code, below, is used to start up and
 * recognize the termination of the player and loader servers 
 */

/* Launch(rport, name, argv, argc, pri, win, stack) -- launch a program.
 *
 * Launch builds a startup message, loads a program, and launches it.
 *
 * Arguments:
 *	rport -- reply port to receive startup message back when the program
 *	         finishes.
 *	name  -- File name for the program to load.
 *	argv  -- list of filenames to pass to loaded program.
 *	argc  -- length of argument list.
 *	pri   -- priority of the new process.
 *	win   -- ToolWindow for the new process, or NULL.
 *	stack -- Stack size for the new process.
 *
 * argv[0] should be the same as the program. pri should normally be 0.
 * stack should normally be at least 4K.
 */
static struct WBStartup *
Launch(rport, name, argv, argc, pri, win, stack)
struct MsgPort *rport;
char *name;
char **argv;
int argc;
long pri;
char *win;
ULONG stack;
{
	ULONG flock;
	struct WBStartup *msg;
	char *s, *namep;
	int i;

	if(!rport)
		return 0;

	/* Get some space to work in -- the startup message */
	msg = (struct WBStartup *)
		AllocMem(sizeof(struct WBStartup), MEMF_PUBLIC|MEMF_CLEAR);
	if(!msg)
		return 0;

	/* Now load the program */
	msg->sm_Segment = (long)LoadSeg(name);
	if(!msg->sm_Segment) 
	{
		FreeStartup(msg);
		return 0;
	}

	/* Allocate and link in the window description */
	if(win) 
	{
		msg->sm_ToolWindow = (char *)AllocMem(strlen(win)+1, MEMF_PUBLIC);
		if(!msg->sm_ToolWindow) 
		{
			FreeStartup(msg);
			return 0;
		}
		strcpy(msg->sm_ToolWindow, win);
	} 
	else
		msg->sm_ToolWindow = 0;

	/* Allocate the arg list */
	msg->sm_ArgList = (struct WBArg *)
		AllocMem((long)(sizeof(struct WBArg) * argc), MEMF_PUBLIC | MEMF_CLEAR);
	if(!msg->sm_ArgList) 
	{
		FreeStartup(msg);
		return 0;
	}

	/* Empty out all args, just in case this aborts, so cleanup
	 * can clean it up
	 */
	msg->sm_NumArgs = argc;
	for(i = 0; i < argc; i++) 
	{
		msg->sm_ArgList[i].wa_Lock = 0;
		msg->sm_ArgList[i].wa_Name = 0;
	}

	/* Get lock and name for wbargs */
	for(i = 0; i < argc; i++) 
	{
		flock = (long)Lock(argv[i],ACCESS_READ);
		if(!flock) 
		{
			FreeStartup(msg);
			return 0;
		}
		msg->sm_ArgList[i].wa_Lock = (long)ParentDir(flock);
		UnLock(flock);
		if(!msg->sm_ArgList[i].wa_Lock) 
		{
			FreeStartup(msg);
			return 0;
		}

		/* Get the tail end of the file name. I could also do an Examine
		 * on the lock, but didn't want to bother allocating a File Info
		 * Block that wouldn't fit under the tracking via the startup
		 * message.
		 */
		namep = argv[i];

		for(s = argv[i]; *s; s++)
			if(*s=='/' || *s==':')
				namep = s+1;

		msg->sm_ArgList[i].wa_Name =
			(char *)AllocMem(strlen(namep)+1, MEMF_PUBLIC);

		if(!msg->sm_ArgList[i].wa_Name) 
		{
			FreeStartup(msg);
			return 0;
		}
		strcpy(msg->sm_ArgList[i].wa_Name, namep);
	}

	/* Create the process. It is now running, but will wait for the
	 * startup message.
	 */
	msg->sm_Process = (struct MsgPort *)
		CreateProc(msg->sm_ArgList[0].wa_Name, pri, msg->sm_Segment, stack);

	if(!msg->sm_Process) 
	{
		FreeStartup(msg);
		return 0;
	}

	/* Initialise the message part of the startup message, and pass it to
	 * the process. At this point it will actually start ding work
	 */
	msg->sm_Message.mn_ReplyPort = rport;
	msg->sm_Message.mn_Length = sizeof(struct WBStartup);
	msg->sm_Message.mn_Node.ln_Type = NT_MESSAGE;

	PutMsg(msg->sm_Process, msg);

	/* return message. Not very useful, but it's as meaningful a response as
	 * any.
	 */
	return msg;
}

static FreeStartup(msg)
struct WBStartup *msg;
{
	/* Free the files. Assumes they have been allocated via Launch, so
	 * they're just AllocMemmed. UnLock all the Locks, and Free the
	 * names.
	 */
	if(msg->sm_ArgList) 
	{
		int i;

		for(i = 0; i < msg->sm_NumArgs; i++) 
		{
			if(msg->sm_ArgList[i].wa_Lock)
				UnLock(msg->sm_ArgList[i].wa_Lock);

			if(msg->sm_ArgList[i].wa_Name)
				FreeMem(msg->sm_ArgList[i].wa_Name,
					(long)strlen(msg->sm_ArgList[i].wa_Name)+1);
		}

		FreeMem(msg->sm_ArgList,
			(long)(sizeof(struct WBArg) * msg->sm_NumArgs));
	}

	/* Here goes the program...
	 */
	if(msg->sm_Segment)
		UnLoadSeg(msg->sm_Segment);

	/* And the name of the window. Again, assuming it's allocated by
	 * Launch, so it just fits in its AllocMemmed buffer.
	 */
	if(msg->sm_ToolWindow)
		FreeMem(msg->sm_ToolWindow,
			(long)(strlen(msg->sm_ToolWindow)+1));

	/* And finally the message itself */
	FreeMem(msg, (long)sizeof(struct WBStartup));
}

/* ------------ message interface --------------------------- */

/* given a port to one of the SMUS server programs, command from requests.h,
 * pointer to data and port to reply to, tell one of the SMUS programs to 
 * do somthing.
 *
 * Note that RPC (Remote Procedure Call) users need not use this routine
 * as it is called directly by the RPC stuff.
 *
 * If the replyport is not NULL, allocate a player request on-the-fly and 
 * uses that for the request as multiple requests may be pending.
 * If the replyport is not NULL, the message is sent and the subroutine
 * returns, then when the server program has completed processing the 
 * command, it will reply to that replyport.  When using it this way,
 * the user is expected to check for the reply message as part of their
 * big event loop.
 *
 * If the replyport is NULL, this signifies that the user wants to wait,
 * it uses an internal request buffer of which there is only one but that's
 * OK as no more than one request can be pending at a time with that model.
 *
 * The RPC interfaces uses TellSMUSProgram in "wait" mode.  You don't
 * have to wait for the song to play if you don't want to, though, because
 * the song player can be told to start playing but reply to the message
 * immediately via it's play-with-quick-return capabilities.
 *
 */
struct PlayerRequest *TellSMUSProgram(port,command,data,replyport)
struct MsgPort *port;
int command;
void *data;
struct MsgPort *replyport;
{
	struct PlayerRequest *msg;
	struct PlayerRequest *rp;
	struct MsgPort *msgp;	/* pointer to reply port we'll use, ours or user's */
	long sigmask, signals;

	if (CheckServerShutdown() && !panic_in_progress && !terminate_in_progress)
		panic(server_died);

	/* set up pointers and get up a request message if there is a reply
	 * port in case there's concurrency */
	if (replyport)
	{
		rp = AllocMem((long)(sizeof(struct PlayerRequest)),MEMF_PUBLIC);
		if (!rp)
			panic("can't alloc SMUS request message");
		msgp = replyport;
	}
	else
	{
		rp = &player_request;
		msgp = reply_request_port;
	}

	/* set up the message */
	rp->command = command;
	rp->status = ES_PENDING;
	rp->data.song_pointer = data;
	rp->message.mn_Length = sizeof(player_request);
	rp->message.mn_ReplyPort = msgp;

	/* send it */
	PutMsg(port,rp);

	/* if they didn't specify a reply port, do a mini Wait loop, being
	 * good neighbors by only looking at our signal bits and control-C
	 */
	if (!replyport)
	{
		sigmask = (1 << launch_reply_port->mp_SigBit)
			| (1 << msgp->mp_SigBit) 
			| SIGBREAKF_CTRL_C;

		do
		{
			signals = Wait(sigmask);

			if (signals & SIGBREAKF_CTRL_C)
				panic("^C abort");

			if (CheckServerShutdown() && !panic_in_progress && !terminate_in_progress)
					panic(server_died);

			while (msg = (struct PlayerRequest *)GetMsg(reply_request_port))
			{
				if (msg == rp)
				{
					break;
				}
			}
		} while (msg != &player_request);
	}

#ifdef DEBUG
	printf("reply status %d\n",rp->status);
#endif

	if ((rp->status == ES_SERVER_ABORTED) && (!panic_in_progress))
		panic(server_died);

	es_errno = rp->status;
	PrintSMUSError(es_errno);
	return(rp);
}

static int CheckServerShutdown()
{
	struct PlayerRequest *msg;

	/* this is static 'cuz one a server did shutdown, a server always
	 * did shutdown */
	static short one_did_shutdown = NO;

	while (msg = (struct PlayerRequest *)GetMsg(launch_reply_port))
	{
		if (player_wbstartup && (struct WBStartup *)msg == player_wbstartup)
		{

#ifdef DEBUG
			printf("got the player's termination message\n");
#endif

			FreeStartup(msg);
			player_wbstartup = NULL;
			one_did_shutdown = YES;
		}
		else if (loader_wbstartup && (struct WBStartup *)msg == loader_wbstartup)
		{

#ifdef DEBUG
			printf("got the loader's termination message\n");
#endif

			FreeStartup(msg);
			loader_wbstartup = NULL;
			one_did_shutdown = YES;
		}
	}
	return(one_did_shutdown);
}

static void LocatePlayerPort()
{
	player_request_port = FindPort(PLAYER_PORTNAME);
	if (!player_request_port)
	{
#ifdef DEBUG
		printf("launching smus player\n");
#endif
		player_wbstartup = Launch(launch_reply_port,player_name,player_launch_argv,1L,0L,(char *)NULL,4096L);

		if (player_wbstartup != (struct WBStartup *)NULL)
		{
			Delay(50);

			player_request_port = FindPort(PLAYER_PORTNAME);

			if (!player_request_port)
				panic("didn't find request port after launching player");

			launched_player = YES;
		}
		else
			panic("launch of player failed");
	}

}

static void LocateLoaderPort()
{
	loader_request_port = FindPort(LOADER_PORTNAME);
	if (!loader_request_port)
	{

#ifdef DEBUG
		printf("launching smus loader\n");
#endif

		loader_wbstartup = Launch(launch_reply_port,loader_name,loader_launch_argv,1,0,NULL,4096);

		if (loader_wbstartup != (struct WBStartup *)NULL)
		{
			Delay(50);

			loader_request_port = FindPort(LOADER_PORTNAME);
			if (!loader_request_port)
				panic("didn't find request port after launching loader");

			launched_loader = YES;
		}
		else
			panic("launch of loader failed");
	}

}

static void SMUSCleanupPorts()
{
	struct Message *msg;
	long sigmask;


	if (launch_reply_port)
	{

		/* if a task shut down, cheat and set panic in progress
		 * so my TellSMUSProgram calls below won't be terminated */
		if (CheckServerShutdown())
			panic_in_progress = YES;

		/* if there isn't a terminate in progress and we didn't launch
		 * the player but we do have a request port for it (meaning
		 * it's running) or we did launch it, send a terminate message
		 * to it & ditto for the loader */

		if (!terminate_in_progress && ((!launched_player && player_request_port) || player_wbstartup))
			TellSMUSProgram(player_request_port,PLEASE_TERMINATE_IMMEDIATELY,NULL,NULL);

		if (!terminate_in_progress && ((!launched_loader && loader_request_port) || loader_wbstartup))
			TellSMUSProgram(loader_request_port,PLEASE_TERMINATE_IMMEDIATELY,NULL);

		while (player_wbstartup || loader_wbstartup)
		{
#ifdef DEBUG
			printf("waiting for player and loader to terminate\n");
#endif
			sigmask = (1 << launch_reply_port->mp_SigBit);

			Wait(sigmask);
			CheckServerShutdown();
		}
		DeletePort(launch_reply_port);
	}

	if (reply_request_port)
	{
		while (GetMsg(reply_request_port)) ;

		DeletePort(reply_request_port);
	}

}

SMUSInitServer()
{
	add_cleanup(SMUSCleanupPorts);

	launch_reply_port = CreatePort(0L,0L);
	if (!launch_reply_port)
		panic("can't create launch reply port");

	reply_request_port = CreatePort(0L,0L);
	if (!reply_request_port)
		panic("can't create request reply port");

	LocateLoaderPort();

	LocatePlayerPort();
}



/* ------------ RPC calls ----------------------------------- */


/* RPC Remote Procedure Call interface to the SMUS loader and player */

/* loads a song, does not insure all samples are loaded */
void *LoadSong(songname)
char *songname;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_LOAD_SONG,songname,NULL);
	if (rp->status == ES_OK)
		return(rp->data.song_pointer);
	else
		return(NULL);
}

/* loads any samples necessary to insure all samples are loaded for songid */
int AttachSamples(songptr)
void *songptr;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_ATTACH_SAMPLES,songptr,NULL);
	return(rp->status);
}

/* unloads song but does not remove song's samples */
UnloadSong(songptr)
void *songptr;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_UNLOAD_SONG,songptr,NULL);
	return(rp->status);
}

/* unloads all samples that aren't referenced by a song or "tagged" */
PurgeUntaggedSamples()
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_PURGE_UNTAGGED_SAMPLES,NULL,NULL);
	return(rp->status);
}


/* loads all samples and songs found in CAT of samples and songs */
LoadCATArchive(catname)
char *catname;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_LOAD_CAT_SAMPLES,catname,NULL);
	return(rp->status);
}


/* loads any samples necessary to insure all samples are loaded for all 
 * songs */
AttachAllSamples(replyport)
struct MsgPort *replyport;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_ATTACH_ALL_SAMPLES,NULL,NULL);
	return(rp->status);
}

/* clears "tag" bits for all samples */
ClearSampleTags()
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_CLEAR_SAMPLE_TAGS,NULL,NULL);
	return(rp->status);
}

/* set "tag" bit for samplename */
SetSampleTag(samplename)
char *samplename;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_TAG_NAMED_SAMPLE,samplename,NULL);
	return(rp->status);
}

/* play a song, don't return until it's finished */
PlaySong(songptr)
void *songptr;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(player_request_port,PLEASE_PLAY_SONG,songptr,NULL);
	return(rp->status);
}

/* shut down the server, free everything, exit */
SMUSTerminateServer()
{
	CheckServerShutdown();

	terminate_in_progress = YES;

	/* if we didn't launch the loader but the loader_request_port is
	 * non-null, we did find the loader, or if loader_wbstartup is
	 * non-null, we did launch the loader and it is apparently still
	 * alive, tell it to shutdown */

	if ((!launched_loader && loader_request_port) || loader_wbstartup)
		TellSMUSProgram(loader_request_port,PLEASE_TERMINATE_IMMEDIATELY,NULL,NULL);

	/* same for the player */

	if ((!launched_player && player_request_port) || player_wbstartup)
		TellSMUSProgram(player_request_port,PLEASE_TERMINATE_IMMEDIATELY,NULL,NULL);
}

/* start playing a song but return immediately */
StartSong(songptr)
void *songptr;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(player_request_port,PLEASE_PLAY_SONG_IMMEDIATE_REPLY,songptr,NULL);
	return(rp->status);
}

SetSampleTagsOfSong(songptr)
void *songptr;
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(loader_request_port,PLEASE_TAG_SONGS_SAMPLES,songptr,NULL);
	return(rp->status);
}

/* terminate the loader now and terminate the player when it's finished
 * the current song (terminates immediately if no song is playing) */
TerminateServerWhenFinished()
{
	terminate_in_progress = YES;

	TellSMUSProgram(player_request_port,PLEASE_TERMINATE_WHEN_FINISHED,NULL,NULL);
	TellSMUSProgram(loader_request_port,PLEASE_TERMINATE_IMMEDIATELY,NULL,NULL);
}

/* abort the currently playing song but don't terminate the server */
AbortSongImmediately()
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(player_request_port,PLEASE_ABORT_IMMEDIATELY,NULL,NULL);
	return(rp->status);
}

/* start a fade out which will eventually abort the song, but don't terminate
 * the server */
StartFadeOut()
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(player_request_port,PLEASE_START_FADEOUT,NULL,NULL);
	return(rp->status);
}


/* don't return until the currently playing song has finished.  Returns
 * immediately if no song is playing */
WaitTilSongFinishes()
{
	struct PlayerRequest *rp;

	rp = TellSMUSProgram(player_request_port,PLEASE_REPLY_WHEN_SONG_FINISHES,NULL,NULL);
	return(rp->status);
}


/* ------------------------- error text --------------------------- */


/* given a SMUS errno, return text of what that error is */
char *SMUSErrorText(errno)
int errno;
{
	char *text;

	switch(errno)
	{

		case ES_INVALID_COMMAND:
			text = "invalid command";
			break;

		case ES_SERVER_ABORTED:
			text = "server aborted";
			break;

		case ES_NOT_IMPLEMENTED:
			text = "command not implemented";
			break;

		case ES_ALREADY_PLAYING:
			text = "player is already playing";
			break;

		case ES_LOAD_FAILED:
			text = "load failed";
			break;

		case ES_NOT_ALL_SAMPLES_PRESENT:
			text = "not all samples present";
			break;

		case ES_SONG_IS_PLAYING:
			text = "song is playing";
			break;

		case ES_BAD_SONG_ADDRESS:
			text = "address of song is invalid";
			break;

		case ES_OK:
			text = "OK";
			break;

		case ES_PENDING:
			text = "request pending";
			break;

		case ES_PROCESSING:
			text = "request being processed";
			break;

		case ES_NOT_FOUND:
			text = "couldn't find specified item";
			break;

		default:
			text = "unknown error type";
			break;

	}
	return(text);
}

PrintSMUSError(errno)
int errno;
{
	puts(SMUSErrorText(errno));
}
