/**************************************************************************
* serial.c:	Serial port access functions.
*		Part of MP, the MIDI Playground.
*
* Author:	Daniel Barrett
* Version:	See the file "version.h".
* Copyright:	None!  This program is in the Public Domain.
*		Please share it with others.
***************************************************************************/

	
#include "mp.h"
#include "midi.h"

static struct MsgPort	*MidiPort	= NULL;
static struct IOExtSer	*Midi		= NULL;
long serialFlags			= 0L;

#define	F_MIDIPORT	1
#define	F_MIDI		2
#define	F_DEVICE	4
#define	F_SERPARAMS	8


BOOL AllDigits(char *str);
char *GetEnv(char *str);
BOOL MakeSerialDeviceName(char *str, char *device, int *unit);

	
/****************************************************************************
 * SerialSetup:  Open the serial port and its message port.
 * Allow for an alternate MIDI device in an environment variable.
 ***************************************************************************/

BOOL SerialSetup(FLAGS sysex)
{
	char deviceName[BUFSIZ], *envVar;
	int unit;

	/* Set the defaults. */

	strcpy(deviceName, DEFAULT_MIDI_DEVICE);
	unit = DEFAULT_MIDI_UNIT;

	/* If environment variable is set, find the real MIDI device info. */

	if ((envVar = GetEnv(MIDI_ENV_VAR))
	&& (!MakeSerialDeviceName(envVar, deviceName, &unit)))
		return(FALSE);

	/* Create a port. */

	if (! (MidiPort = CreatePort(0, 0)) )
	{
		fprintf(stderr, "Cannot create port\n");
		SerialShutdown();
		return(FALSE);
	}
	else
		serialFlags |= F_MIDIPORT;

	/* Create an extended I/O structure. */

	if (! (Midi = (struct IOExtSer *)
	CreateExtIO(MidiPort, sizeof(struct IOExtSer))))
	{
		fprintf(stderr, "Cannot create extended I/O structure\n");
		SerialShutdown();
		return(FALSE);
	}
	else
		serialFlags |= F_MIDI;

	/* Open the serial device. */

	Midi->io_SerFlags = SERF_SHARED;
	if (OpenDevice(deviceName, unit, (struct IORequest *)Midi, 0))
	{
		fprintf(stderr,
			"Cannot open serial device \"%s\", unit %d.\n",
			deviceName, unit);
		SerialShutdown();
		return(FALSE);
	}
	else
		serialFlags |= F_DEVICE;

	/* Set the serial device parameters. */

	Midi->io_SerFlags		= SERF_RAD_BOOGIE;
	if (sysex)
	{
		Midi->io_TermArray.TermArray0	= 0xF7F7F7F7;
		Midi->io_TermArray.TermArray1	= 0xF7F7F7F7;
		Midi->io_SerFlags |= SERF_EOFMODE;
	}
	Midi->io_Baud			= MIDI_BAUD_RATE;
	Midi->io_ExtFlags		= 0;	/* For future compatibility */
	Midi->IOSer.io_Command		= SDCMD_SETPARAMS;

	if (DoIO((struct IORequest *)Midi) != 0)
 	{
		fprintf(stderr, "Cannot set serial parameters.\n");
		SerialShutdown();
		return(FALSE);
	}
	else
		serialFlags |= F_SERPARAMS;

	return(TRUE);
}


/****************************************************************************
 * SerialShutdown:  Close the serial port and its message port.
 ***************************************************************************/

void SerialShutdown(void)
{
	if (serialFlags & F_DEVICE)
	{
		ResetSerialPort();
		AbortIO((struct IORequest *)Midi);
		WaitIO((struct IORequest *)Midi);
		CloseDevice((struct IORequest *)Midi);
	}

	if (serialFlags & F_MIDI)
		DeleteExtIO((struct IORequest *)Midi);

	if (serialFlags & F_MIDIPORT)
		DeletePort(MidiPort);
}

/****************************************************************************
 * ResetSerialPort:  Clear all data from the serial port.
 ***************************************************************************/

void ResetSerialPort(void)
{
	Midi->IOSer.io_Command = CMD_CLEAR;
	DoIO((struct IORequest *)Midi);
}


/****************************************************************************
 * Is any data there?
 ***************************************************************************/

long AnyMidiData(void)
{
	Midi->IOSer.io_Command = SDCMD_QUERY;
	DoIO((struct IORequest *)Midi);
	return(Midi->IOSer.io_Actual);
}

	
/****************************************************************************
 * PrepareToReadMidi:	Prepare a READ request for the MIDI port.
 * PrepareToWriteMidi:	Prepare a WRITE request for the MIDI port.
 ***************************************************************************/

void PrepareToReadMidi(UBYTE buf[], int len)
{
	Midi->IOSer.io_Command = CMD_READ;
	Midi->IOSer.io_Data = (APTR)buf;
	Midi->IOSer.io_Length = len;
}


void PrepareToWriteMidi(UBYTE buf[], int len)
{
	Midi->IOSer.io_Command = CMD_WRITE;
	Midi->IOSer.io_Data = (APTR)buf;
	Midi->IOSer.io_Length = len;
}


/****************************************************************************
* DoTheIO:  General-purpose MIDI I/O routine.  Quits on ^C.
****************************************************************************/

long DoTheIO(void)
{
	int mask, temp;
	long bytesDone = 0L;

	mask = SIGBREAKF_CTRL_C | (1L << MidiPort->mp_SigBit);
	SendIO((struct IORequest *)Midi);

	while (1)
	{
		temp = Wait(mask);
		if (temp & SIGBREAKF_CTRL_C)
		{
			bytesDone = CTRL_C_NO_BYTES;
			break;
		}

		if (CheckIO((struct IORequest *)Midi))
		{
			WaitIO((struct IORequest *)Midi);
			bytesDone = Midi->IOSer.io_Actual;
			break;
		}
	}

	AbortIO((struct IORequest *)Midi);
	WaitIO((struct IORequest *)Midi);
	return(bytesDone);
}


/****************************************************************************
* Fast serial reading routine, from idea on 1.3 RKM's page 863.
* If any data is waiting, get all of it with DoIO().  Otherwise, post an
* asynchronous request for 1 byte.  Repeat this in the calling program.
****************************************************************************/

long FastSerialRead(UBYTE buf[])
{
	long bytesWaiting, bytesRead;

	if ((bytesWaiting = AnyMidiData()) > 0)
	{
		PrepareToReadMidi(buf, MIN(bytesWaiting, BUFSIZ));
		DoIO((struct IORequest *)Midi);
		return(Midi->IOSer.io_Actual);
	}
	else
	{
		PrepareToReadMidi(buf, 1);
		return(DoTheIO());
	}
}

	
/****************************************************************************
* Allow the use of another MIDI device than serial.device.
* Environment variable syntax is "DEVICENAME:UNITNUMBER".
* For example:  "midi.device:2".
*
* "device" MUST be preallocated.
****************************************************************************/

BOOL MakeSerialDeviceName(char *str, char *device, int *unit)
{
	while (str && *str && (*str != ':'))
		*(device++) = *(str++);

	*device = '\0';

	if ((*str != ':') || *(str+1) == '\0')
	{
		fprintf(stderr,
			"Your MIDI device name (variable "
			MIDI_ENV_VAR
			") is missing a colon\nand/or a unit number.\n");
		return(FALSE);
	}
	str++;

	if (!AllDigits(str))
	{
		fprintf(stderr,
			"Your MIDI device unit number (variable "
			MIDI_ENV_VAR
			") must be\na positive integer.\n");
		return(FALSE);
	}
	else
		*unit = atoi(str);

	return(TRUE);
}


/* AllDigits:	Return TRUE iff the string "str" consists only of digits. */

BOOL AllDigits(char *str)
{
	if ((!str) || (*str == '\0'))		/* NULL or empty string. */
		return(FALSE);
	else
		while (*str)			/* For each character... */
			if (!isdigit(*str))	/*  if not a digit...    */
				return(FALSE);	/*  goodbye!             */
			else
				str++;
	return(TRUE);				/* All were digits.      */
}
