/*
 *  FILE: communic.c
 *  Support routines for accessing, reading and writing to the serial port.
 *
 *  NOTE: these routines provide EXCLUSIVE access to the serial port for one
 *  task only.	This shouldn't be too great a problem given the nature of the
 *  serial port.
 *
 *  Public Domain, but keep our names in it as the original authors.
 *  11-May-87	Scotte Zinn	    created
 *  15-Mar-88	Jan Sven Trabandt   changed c_set_error to a macro
 *				    made sure it works with short (16 bit) ints
 *  27-Apr-88	Jan Sven Trabandt   lots of goodies
 *  31-Oct-88	Jan Sven Trabandt   added to gimme.lib (finally)
 */


#define I_AM_COMMUNIC
#include "gimmelib/gimmefuncs.h"
#include "gimmelib/communic.h"
#include <devices/serial.h>


#define C_NOT_OPEN	0
#define BUFFER_LENGTH	4096
#define INIT_VALUE	29451

#define initialized()       ((SHORT)initial != 0)
#define channel_open()      ((SHORT)system_state != C_NOT_OPEN)
#define c_set_error(err)    (c_error = err)

static struct IOExtSer	*IORser = NULL;
static struct MsgPort	*SPort = NULL;
static struct timerequest *treq = NULL;
static char EOLchar = DEFAULT_EOL;
static SHORT system_state = C_NOT_OPEN;
static SHORT initial = 0;

short c_error;

/* forward declarations */
static short c_getchar();


short c_init()
{
    if( !initialized() ) {
	/* Set system state to channel not open and set initialized flag */
	Forbid();
	initial = INIT_VALUE;
	system_state = C_NOT_OPEN;
	Permit();
    }
    return( c_set_error(C_ERR_OK) );
} /* c_init */


short c_open( parms )
    struct c_parameters *parms;
{
    short   error;

    if( !parms ) {
	return( c_set_error(C_ERR_BADPARM) );
    }
    if( !initialized() )
	return( c_set_error(C_ERR_INITIAL) );
    if( channel_open() )
	return( c_set_error(C_ERR_OPEN) );

    /* Now access timer device for future time-outs and to get a Port */
    if( !(treq = accessTimer(UNIT_VBLANK, &SPort)) ) {
	return( c_set_error(C_ERR_CANT) );
    }

    IORser = (struct IOExtSer *) CreateExtIO( SPort,
					(ULONG)sizeof(struct IOExtSer) );
    if( !IORser ) {
	releaseTimer( treq, NULL );
	return( c_set_error(C_ERR_CANT) );
    }

    if( parms->C_MODE == C_SER_7 ) {
	IORser->io_SerFlags = SERB_7WIRE | SERB_XDISABLED;
    }
    if( OpenDevice(SERIALNAME, 0L, IORser, 0L) ) {
	DeleteExtIO( IORser, (ULONG)sizeof(struct IOExtSer) );
	releaseTimer( treq, NULL );
	return( c_set_error(C_ERR_CANT) );
    }

    system_state = -1;			/* Now set channel opened */

    if( error = c_setup(parms) ) {
	CloseDevice( IORser );
	DeleteExtIO( IORser, (ULONG)sizeof(struct IOExtSer) );
	releaseTimer( treq, NULL );
	system_state = C_NOT_OPEN;
	return( c_set_error(error) );
    }
    EOLchar = DEFAULT_EOL;

    /* Channel is now successfully opened */
    return( c_set_error(C_ERR_OK) );
} /* c_open */


short c_setup( parms )
    struct c_parameters *parms;
{
    if( !(initialized() && channel_open()) )    /* important to check here */
	return( c_set_error(C_ERR_CHAN) );
    if( !parms ) {
	return( c_set_error(C_ERR_BADPARM) );
    }

    /* Now set parameters as requested */
    IORser->io_ReadLen	= parms->C_RLEN;
    IORser->io_BrkTime	= 750000L;
    IORser->io_Baud	= parms->C_BAUD;
    IORser->io_WriteLen = parms->C_WLEN;
    IORser->io_StopBits = parms->C_STOP;
    IORser->io_RBufLen	 = BUFFER_LENGTH;

    /* Setup parity bits with validation */
    IORser->io_SerFlags &= ~(SERB_PARTY_ODD & SERB_PARTY_ON);

    switch( parms->C_PARITY ) {
      case C_NO_PARITY:
	break;
      case C_ODD_PARITY:
	IORser->io_SerFlags |= SERB_PARTY_ODD | SERB_PARTY_ON;
	break;
      case C_EVEN_PARITY:
	IORser->io_SerFlags |= SERB_PARTY_ON;
	break;
      default:
	return( c_set_error(C_ERR_PARITY) );
    } /* switch */

    IORser->IOSer.io_Command = SDCMD_SETPARAMS;
    IORser->io_TermArray.TermArray0 = 0x51040303;
    IORser->io_TermArray.TermArray1 = 0x03030303;

    if( DoIO(IORser) )
	return( c_set_error(C_ERR_PARAMS) );

    /* Parameters were changed as needed */
    return( c_set_error(C_ERR_OK) );
} /* c_setup */


/* internal use only!
static short c_getchar( cinput, secs, micros )
    char    *cinput;
    ULONG   secs, micros;
{
    APTR    myptr;
    short   iodone = 0;

    /* Send request for read a character */
    IORser->IOSer.io_Data = (APTR) cinput;
    IORser->IOSer.io_Length = 1;
    IORser->IOSer.io_Command = CMD_READ;
    SendIO( IORser );

    if( CheckIO(IORser) ) {
	Remove( IORser );
	return( c_set_error(C_ERR_OK) );
    } else {
	timeDelayAsync( secs, micros, UNIT_VBLANK, treq );
	WaitPort( SPort );
	while( myptr = (APTR) GetMsg(SPort) ) {
	    if( myptr == (APTR) IORser ) {
		iodone = -1;
	    }
	} /* while */
	if( iodone ) {
	    killTimeDelay( treq );
	    return( c_set_error(C_ERR_OK) );
	}
    }

    /* Abort the requested IO and return error condition */
    AbortIO( IORser );
    Remove( IORser );
    return( c_set_error(C_ERR_GET) );
} /* c_getchar */


short c_getc( cinput )
    char    *cinput;
{
#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
    if( !buffer ) {
	return( c_set_error(C_ERR_BADPARM) );
    }
#endif
    return( c_getchar(cinput, BIG_SECS, BIG_MICROS) );
} /* c_getc */


short c_emptyc()
{
    short   err;
    char    cinput;

#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
#endif
    /* Empty the buffer */
    while( (err = c_getchar(&cinput, SMALL_SECS, SMALL_MICROS)) == C_ERR_OK ) {
    } /* while */

    /* Check for a buffer empty error and replace with ok status */
    if( err == C_ERR_GET ) {
	c_set_error(err = C_ERR_OK);
    }
    /* Return status of empty */
    return( err );
} /* c_emptyc */


short c_putc( coutput )
    char coutput;
{
#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
#endif
    /* Now send character thru channel */
    IORser->IOSer.io_Data = (APTR) &coutput;
    IORser->IOSer.io_Length = 1;
    IORser->IOSer.io_Command = CMD_WRITE;
    if( DoIO(IORser) ) {
	return( c_set_error(C_ERR_PUT) );
    }
    return( c_set_error(C_ERR_OK) );
} /* c_putc */


short c_gets( buffer, num_to_get )
    char    *buffer;
    SHORT   num_to_get;
{
    register SHORT  i;
    short	    error;

#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
    if( !buffer ) {
	return( c_set_error(C_ERR_BADPARM) );
    }
#endif
    if( num_to_get <= 0 ) {
	return( c_set_error(C_ERR_ILLEGAL) );
    }

    /* Now get the specified number of characters */
    for( i = num_to_get; --i >= 0; ++buffer ) {
	if( error = c_getchar(buffer, BIG_SECS, BIG_MICROS) ) {
	    return( c_set_error(error) );
	}
    } /* for */

    return( c_set_error(C_ERR_OK) );
} /* c_gets */


short c_getline( buffer, num_to_get )
    char    *buffer;
    SHORT   num_to_get;
{
    register SHORT  i;
    short	    error;

#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
    if( !buffer ) {
	return( c_set_error(C_ERR_BADPARM) );
    }
#endif
    if( num_to_get <= 0 ) {
	return( c_set_error(C_ERR_ILLEGAL) );
    }

    /* Now get the specified number of characters */
    for( i = num_to_get; --i >= 0; ++buffer ) {
	if( error = c_getchar(buffer, SMALL_SECS, SMALL_MICROS) ) {
	    *buffer = '\0';
	    return( c_set_error(error) );
	}
	if( *buffer == EOLchar ) {
	    break;
	}
    } /* for */

    return( c_set_error(C_ERR_OK) );
} /* c_getline */


short c_setEOL( character )
    char character;
{
#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
#endif
    EOLchar = character;
    return( c_set_error(C_ERR_OK) );
} /* c_setEOL */


short c_puts( buffer, num_to_put )
    char    *buffer;
    SHORT   num_to_put;
{
    short   error;

#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
    if( !buffer ) {
	return( c_set_error(C_ERR_BADPARM) );
    }
#endif
    if( num_to_put <= 0 ) {
	return( c_set_error(C_ERR_ILLEGAL) );
    }
    /* Now send the characters thru channel */
    IORser->IOSer.io_Data = (APTR) buffer;
    IORser->IOSer.io_Length = num_to_put;
    IORser->IOSer.io_Command = CMD_WRITE;
    if( DoIO(IORser) ) {
	return( c_set_error(C_ERR_PUT) );
    }
    return( c_set_error(C_ERR_OK) );
} /* c_puts */


short c_close()
{
    short   err;

#ifdef GIMME_WIMPY
    if( !(initialized() && channel_open()) )
	return( c_set_error(C_ERR_CHAN) );
#endif

    if( err = c_emptyc() ) {            /* Empty the buffer */
	return( err );
    }
    /* Now close device and channel */
    CloseDevice( IORser );
    DeleteExtIO( IORser, (ULONG)sizeof(struct IOExtSer) );
    releaseTimer( treq, NULL );         /* Now close timer device */
    SPort = NULL;
    system_state = C_NOT_OPEN;
    return( c_set_error(C_ERR_OK) );
} /* c_close */


short c_done()
{
    if( initialized() ) {
	/* Set system state to channel not open and clear initialized flag */
	Forbid();
	initial = 0;
	system_state = C_NOT_OPEN;
	Permit();
    }
    return( c_set_error(C_ERR_OK) );
} /* c_done */
