/****************************************************************************/
/* Copyright 2003 MBARI														*/
/****************************************************************************/
/* Summary	: Utility Routines for OASIS Mooring Controller					*/
/* Filename : utils.c														*/
/* Author	: Robert Herlien (rah)											*/
/* Project	: OASIS Mooring													*/
/* Revision : 1.0															*/
/* Created	: 07/11/2016													*/
/*																			*/
/* MBARI provides this documentation and code "as is", with no warranty,	*/
/* express or implied, of its quality or consistency. It is provided without*/
/* support and without obligation on the part of the Monterey Bay Aquarium	*/
/* Research Institute to assist in its use, correction, modification, or	*/
/* enhancement. This information should not be published or distributed to	*/
/* third parties without specific written permission from MBARI.			*/
/*																			*/
/****************************************************************************/
/* Modification History:													*/
/* 07jan2003 rah - created OASIS3 version from OASIS utils.c				*/
/* 11jul2016 rah - created OASIS5 version from OASIS3 version				*/
/* $Log$
 */
/****************************************************************************/

#include <mbariTypes.h>					/* MBARI type definitions			*/
#include <mbariConst.h>					/* MBARI constants					*/
#include <oasis.h>						/* OASIS controller definitions		*/
#include <otask.h>						/* OASIS Multitasking definitions	*/
#include <serial.h>						/* OASIS serial I/O definitions		*/
#include <utils.h>						/* OASIS Utility routines			*/
#include <remote.h>						/* Remote I/O library			*/
#include <debug.h>

//#include <stdio.h>						/* Standard I/O library				*/
//#include <stdarg.h>						/* vararg stuff						*/
//#include <stdlib.h>						/* Standard C library				*/
#include <string.h>						/* String library functions			*/
#include <ctype.h>						/* Standard ctype.h defs			*/

#define LOC_SER_POLL	2				/* Poll rate for local serial ports */	
#define REM_SER_POLL	10				/* Poll rate for remote serial ports*/	

#define SER_POLL(port)	(isPhysSer(port) ? LOC_SER_POLL : REM_SER_POLL)
#define MAX_FLUSH_TIME	(30 * TICKS_PER_SEC)
	

/********************************/
/*		External Data			*/
/********************************/


/************************************************************************/
/* Function	   : doputc													*/
/* Purpose	   : Write one character to serial port, no '\n' conversion */
/* Inputs	   : Character to output, ptr to task's serial descriptor	*/
/* Output	   : None													*/
/* Comments	   : Waits until character output							*/
/************************************************************************/
void doputc(Int32 c, SerDesc *sdp)
{
	char		cchar;

	if (sdp != NULL)
	{
		if (isPhysSer(sdp->serport))
		{
			while( !ser_putc(sdp->serport, c) )
				task_delay(10);
		}
		else
		{
			cchar = c;
			remSerWrite(getVirtSerPort(sdp->serport), &cchar, 1);
		}
	}

} /* doputc() */


/************************************************************************/
/* Function	   : newlinep												*/
/* Purpose	   : Write '\n' to serial port								*/
/* Inputs	   : Ptr to task's serial descriptor						*/
/* Outputs	   : None													*/
/* Comments	   : Will wait forever until it can output serial character */
/************************************************************************/
void newlinep(SerDesc *sdp)
{
	if (sdp != NULL)
	{
		switch( sdp->sermode & NMODE )
		{
		  case NOLF:
			  break;

		  case NEWCR:
			  if ( sdp->lastChar != '\r' )
				  doputc('\r', sdp);
			  break;

		  case AUTOCR:
			  doputc('\r', sdp);				/* NOTE fall through!	*/

		  default:
			  doputc('\n', sdp);
		}
	}

} /* newlinep() */


/************************************************************************/
/* Function	   : xputcp													*/
/* Purpose	   : Write one character to serial port						*/
/* Inputs	   : Character to output, ptr to serial descriptor			*/
/* Output	   : None													*/
/* Comments	   : Waits until character output							*/
/************************************************************************/
void xputcp(Int32 c, SerDesc *sdp)
{
	if (sdp != NULL)
	{
		if ( c == '\n' )
			newlinep(sdp);
		else
			doputc(c, sdp);
	
		sdp->lastChar = c;
	}

} /* xputcp() */


/************************************************************************/
/* Function	   : xputc													*/
/* Purpose	   : Write one character to serial port						*/
/* Inputs	   : Character to output									*/
/* Output	   : None													*/
/* Comments	   : Waits until character output							*/
/************************************************************************/
void xputc(Int32 c)
{
	SerDesc		*sdp;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) != NULL)
		xputcp(c, sdp);

} /* xputc() */


/************************************************************************/
/* Function	   : getOneSerChar											*/
/* Purpose	   : Helper function for xgetc_tmout; get one serial char	*/
/* Inputs	   : Serial port number										*/
/* Output	   : Character, or ERROR if none							*/
/************************************************************************/
MLocal Int32 getOneSerChar(Port_t port)
{
	char		c;

	if (isPhysSer(port))
		return(ser_getc(port));
	else
	{
		if (remSerRead(getVirtSerPort(port), &c, 1) < 1)
			return(ERROR);
		return((Int32)c & 0xff);
	}

} /* getOneSerChar() */


/************************************************************************/
/* Function	   : xgetc_tmout											*/
/* Purpose	   : Read one character from serial port with timeout		*/
/* Inputs	   : Timeout in seconds										*/
/* Output	   : Character												*/
/* Comments	   : Returns ERROR if timed out								*/
/*				 If tmout == NO_TIMEOUT, will wait 18 hours				*/
/*				 If tmout == 0, get just what's currently in input buffer*/
/************************************************************************/
Int32	xgetc_tmout(Nat32 tmout)
{
	SerDesc		*sdp;
	Port_t		port;
	Nat32		endTick;
	Int32		delay;
	Reg Int32	c;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return(ERROR);

	port = sdp->serport;

	endTick = xTaskGetTickCount() + (tmout * TICKS_PER_SEC);
	delay = SER_POLL(port);

	if ( tmout == 0 )
		return(getOneSerChar(port));

	while (xTaskGetTickCount() < endTick)
	{
		if ( (c = getOneSerChar(port)) != ERROR )
			return( c );
		task_delay( delay );
	}

	return( c );

} /* xgetc_tmout() */


/************************************************************************/
/* Function	   : xgetc													*/
/* Purpose	   : Read one character from serial port					*/
/* Inputs	   : None													*/
/* Output	   : Character												*/
/* Comments	   : Waits until character available						*/
/************************************************************************/
Int32 xgetc(void)
{
	return( xgetc_tmout(NO_TIMEOUT) );

} /* xgetc() */


/************************************************************************/
/* Function	   : xputs													*/
/* Purpose	   : Write string to serial out								*/
/* Inputs	   : String ptr												*/
/* Outputs	   : None													*/
/* Comments	   : Will wait forever until it can output serial characters*/
/************************************************************************/
void xputs( const char *s )
{
	SerDesc		*sdp;
	Port_t		port;
	const char	*p;
	char		*sndBuf, *q;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return;
	
	port = sdp->serport;

	if (isPhysSer(port))
	{
		while (*s)
			xputcp(*s++, sdp);
	}
	else if ((sdp->sermode & NMODE) == LF_NORMAL)
		remSerWrite(getVirtSerPort(port), s, strlen(s));

	else						/* Need to optimize for ModBus if we're */
	{							/* doing CR/LF translations				*/
		if ((sndBuf = malloc(2*strlen(s))) == NULL)
			return;

		for (p = s, q = sndBuf; *p; p++)
		{
			if (*p != '\n')
				*q++ = *p;
			else switch (sdp->sermode & NMODE)
			{
			  case NOLF:
				  break;

			  case NEWCR:
				  if ( p[-1] != '\r' )
					  *q++ = '\r';
				  break;

			  case AUTOCR:
				  *q++ = '\r';
				  *q++ = '\n';
				  break;
			}
		}

		remSerWrite(getVirtSerPort(port), sndBuf, q - sndBuf);
		free(sndBuf);
	}

} /* xputs() */


/************************************************************************/
/* Function	   : xputn													*/
/* Purpose	   : Write fixed length message to serial out, no CR/LF xlate*/
/* Inputs	   : Buffer ptr, buffer length								*/
/* Outputs	   : None													*/
/* Comments	   : Will wait forever until it can output serial characters*/
/************************************************************************/
void xputn( const char *s, Int32 len )
{
	SerDesc		*sdp;
	Port_t		port;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return;
	
	port = sdp->serport;

	if (isPhysSer(port))
		ser_write(port, s, len, NO_TIMEOUT);
	else
		remSerWrite(getVirtSerPort(port), s, len);

} /* xputn() */


/************************************************************************/
/* Function	   : xgetn_tmout											*/
/* Purpose	   : Read n characters into buffer with timeout				*/
/* Inputs	   : Buffer ptr, buffer length, timeout in seconds			*/
/* Outputs	   : Number characters read (0 if no chars before timeout)	*/
/* Comments	   : Does no line editing, doesn't echo						*/
/*				 If tmout == NO_TIMEOUT, will wait 18 hours				*/
/*				 If tmout == 0, get just what's currently in input buffer*/
/*				 Operates a byte at a time because PicoDOS TURxGetBlock */
/*				 doesn't work with a timeout of 0, and is inefficient	*/
/*				 with a timeout of 1 ms									*/
/************************************************************************/
Int32 xgetn_tmout( char *s, Int32 len, Nat32 tmout )
{
	SerDesc		*sdp;
	Port_t		serPort;
	char		*p;
	Int32		totBytes;
	Nat32		endTick, bytesRead;
	RemSerHandle rsh;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return(ERROR);
	
	serPort = sdp->serport;

	if (isPhysSer(serPort))
		return(ser_read(serPort, s, (Int32)len, (Int32)tmout * TICKS_PER_SEC));
	else
	{
		endTick = xTaskGetTickCount() + ((Nat32)tmout * TICKS_PER_SEC);

		if ((rsh = getVirtSerPort(serPort)) == NULL)
			return(0);

		for (totBytes = 0, p = s; totBytes < len; )
		{
			if ((bytesRead = remSerRead(rsh, p, len - totBytes)) > 0)
			{
				p += bytesRead;
				totBytes += bytesRead;
			}
			else if ((Int32)(xTaskGetTickCount() - endTick) < 0)
				task_delay(REM_SER_POLL);
			else
				return(totBytes);
		}
		return(totBytes);
	}

} /* xgetn_tmout() */


/************************************************************************/
/* Function	   : xgets_tmout_flg										*/
/* Purpose	   : Read one line of user input into buffer with timeout	*/
/* Inputs	   : Buffer ptr, buffer length, timeout in seconds, flags	*/
/* Outputs	   : Number characters read, or ERROR if no chars before timeout*/
/* Comments	   : Gets input until newline, return, end of buffer, or tmout*/
/*				 If tmout == NO_TIMEOUT, will wait 18 hours				*/
/*				 If tmout == 0, get just what's currently in input buffer*/
/*				 See oasis.h for flags.	 Currently just INCLUDE_TERMCHAR*/
/* Note on remote I/O using ModBus (4/23/09) --							*/
/* We're forced to get one char at a time, since we have to look for	*/
/* line terminators.  This is inefficient on ModBus, but mitigated by	*/
/* the fact that remSerRead() buffers the I/O							*/
/************************************************************************/
Int32 xgets_tmout_flg( char *s, Int32 len, Nat32 tmout, Word flags )
{
	SerDesc		*sdp;
	Port_t		serPort;
	Nat32		nchars;					/* Num chars read				*/
	Reg Int32	c;						/* Most recently read character */
	Nat32		endTick;				/* When to stop					*/
	RemSerHandle rsh;
	Int32		delay;
	Nat32		rtn;
	char		cchar;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return(ERROR);
	
	serPort = sdp->serport;

	nchars = 0;
	endTick = xTaskGetTickCount() + ((Nat32)tmout * TICKS_PER_SEC);

	rsh = getVirtSerPort(serPort);
	delay = SER_POLL(serPort);

	while (xTaskGetTickCount() <= endTick)
	{
		s[nchars] = '\0';					/* NULL terminate string*/
		if (isPhysSer(serPort))
			c = ser_getc(serPort);
		else
		{
			rtn = remSerRead(rsh, &cchar, 1);
			c = (rtn > 0) ? (Int32)cchar & 0xff : ERROR;
		}

		switch(c)
		{
		  case '\n':
		  case '\r':
			  if ( sdp->sermode & ECHO )
				  newlinep(sdp);
			  if ( flags & INCLUDE_TERMCHAR )
			  {
				  s[nchars++] = (Byte)c;
				  s[nchars] = '\0';
			  }
			  return(nchars);

		  case '\b':
			  if (nchars > 0)
			  {
				  nchars--;
				  if (sdp->sermode & ECHO)
					  xputs("\b \b");
			  }
			  break;

		  case ERROR:
			  task_delay(delay);				/* Wait for more serial */
			  break;

		  default:
			  s[nchars++] = (Byte)c;			/* Normal char, put in buffer*/
			  s[nchars] = '\0';
			  if ( sdp->sermode & ECHO )
				  xputcp( c, sdp );
			  if ( nchars >= len-1 )
				  return( nchars );
			  break;
		
		} /* switch */
	} /* for */

	if (nchars == 0)
		return(ERROR);
	
	return(nchars);

} /* xgets_tmout_flg() */


/************************************************************************/
/* Function	   : xgets_tmout											*/
/* Purpose	   : Read one line of user input into buffer with timeout	*/
/* Inputs	   : Buffer ptr, buffer length, timeout in seconds			*/
/* Outputs	   : Number characters read, or ERROR if no chars before timeout*/
/* Comments	   : Gets input until newline, return, end of buffer, or tmout*/
/*				 If tmout == NO_TIMEOUT, will wait 18 hours				*/
/*				 If tmout == 0, get just what's currently in input buffer*/
/************************************************************************/
Int32 xgets_tmout(char *s, Int32 len, Nat32 tmout)
{
	return(xgets_tmout_flg(s, len, tmout, 0));

} /* xgets_tmout() */


/************************************************************************/
/* Function	   : xgets													*/
/* Purpose	   : Read one line into buffer								*/
/* Inputs	   : Buffer pointer, buffer length							*/
/* Outputs	   : Number characters read									*/
/* Comments	   : Gets input until newline, return, or end of buffer		*/
/*				 Will wait forever for above conditions					*/ 
/************************************************************************/
Int32 xgets(char *s, Int32 len)
{
	return(xgets_tmout_flg(s,len,NO_TIMEOUT,0));

} /* xgets() */


/************************************************************************/
/* Function	   : xTxStatus												*/
/* Purpose	   : Get number characters waiting to send on serial port	*/
/* Inputs	   : None													*/
/* Output	   : Number characters left to send							*/
/************************************************************************/
int xTxStatus(void)
{
	SerDesc		*sdp;
  
	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return(0);

	if (isPhysSer(sdp->serport))
		return(ser_sndsts(sdp->serport));

	return(0);					/* For now, assume remote CPU buffers them*/

} /* xTxStatus() */


/************************************************************************/
/* Function    : xRxAvail												*/
/* Purpose     : Return number of bytes available on serial port        */
/* Inputs      : Serial port number                                     */
/* Outputs     : None                                                   */
/************************************************************************/
int xRxAvail(void)
{
	SerDesc		*sdp;
	Port_t		port;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return(0);

	port = sdp->serport;
	if (isPhysSer(port))
		return(serRxCount(port));

	return(remSerRxCount(getVirtSerPort(port)));

} /* xRxAvail() */


/************************************************************************/
/* Function	   : xflush_ser												*/
/* Purpose	   : Discard serial input stream							*/
/* Inputs	   : Number of ticks to wait for input to flush				*/
/* Output	   : None													*/
/* Comments	   : If tmout == 0, just discard current input				*/
/************************************************************************/
void xflush_ser(Nat32 timeout)
{
	SerDesc		*sdp;
	Port_t		serPort;
	Nat32		flushTick, startTick;

	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return;
	
	serPort = sdp->serport;

	if (isPhysSer(serPort))
	{
		serRxFlush(serPort);
		startTick = flushTick = xTaskGetTickCount();
		
		while ( (xTaskGetTickCount() - flushTick < (Nat32)timeout) && 
				(xTaskGetTickCount() - startTick < MAX_FLUSH_TIME) )
		{
			task_delay( 10 );
			while ( ser_getc(serPort) != ERROR )
				flushTick = xTaskGetTickCount();
		}
	}
	else
		remSerIFlush(getVirtSerPort(serPort));

} /* xflush_ser() */


/************************************************************************/
/* Function	   : xdrain_ser												*/
/* Purpose	   : Wait for serial output stream to finish sending		*/
/* Inputs	   : Number of ticks to wait for output to drain			*/
/* Output	   : None													*/
/************************************************************************/
void xdrain_ser(Nat32 tmout)
{
	SerDesc		*sdp;
	Port_t		serPort;
	Nat32		i;
  
	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return;
	
	serPort = sdp->serport;

	if (isPhysSer(serPort))
	{
		for ( i = tmout; (ser_sndsts(serPort) > 0) && i; i-- )
			task_delay( 1 );
	}
	else
		remSerDrain(getVirtSerPort(serPort), (Nat32)tmout);

} /* xdrain_ser() */


/************************************************************************/
/* Function	   : xser_break												*/
/* Purpose	   : Send Break signal to a serial port						*/
/* Inputs	   : Length of break in ticks								*/
/* Output	   : None													*/
/************************************************************************/
void xser_break(Int32 breakTicks)
{
	SerDesc		*sdp;
	Port_t		serPort;
  
	if ((sdp = pvTaskGetThreadLocalStoragePointer(NULL, ConsoleP)) == NULL)
		return;
	
	serPort = sdp->serport;

	if (isPhysSer(serPort))
		ser_break(serPort, breakTicks);
	else
		remSerBreak(getVirtSerPort(serPort), breakTicks*(1000/TICKS_PER_SEC));

} /* xser_break() */


/************************************************************************/
/* Function	   : delimit												*/
/* Purpose	   : Determine if character is a delimiter					*/
/* Inputs	   : Character												*/
/* Outputs	   : TRUE if character is ',', NULL, or space				*/
/************************************************************************/
MBool delimit(Reg char c)
{
  return(isspace(c) || (c == '\0') || (c == ','));

} /* delimit() */


/************************************************************************/
/* Function	   : cmp_ulc												*/
/* Purpose	   : Compare strings, case insensitive						*/
/* Inputs	   : String ptr (mixed case, leading blanks),				*/
/*				 string ptr (mixed case, no blanks)						*/
/* Outputs	   : NULL if strings don't match, else ptr to remainder of	*/
/*				 user input string										*/
/************************************************************************/
char *cmp_ulc(const char *s, const char *cs)
{
	deblank(s);
	while( *cs )
	{
		if ( toupper(*s) != toupper(*cs) )
			return( NULL );
		s++;
		cs++;
	}

	return((char *)(delimit(*s) ? s : NULL));

} /* cmp_ulc() */


/************************************************************************/
/* Function    : tohex													*/
/* Purpose     : Convert an ASCII character to hex equivalent			*/
/* Inputs      : ASCII character, radix									*/
/* Outputs     : Hex number, or ERROR if not valid hex digit			*/
/************************************************************************/
MLocal Int16 tohex(Int16 c, Nat16 radix)
{
	Reg Int16	rtn;

	if (isdigit(c))
		rtn = c - '0';
	else if (isxdigit(c))
		rtn = toupper(c) - 'A' + 10;
	else
		return(ERROR);

	return((rtn < radix) ? rtn : ERROR);

} /* tohex() */


/************************************************************************/
/* Function    : getByte												*/
/* Purpose     : Get one byte from first two ASCII chars in buffer		*/
/* Inputs      : Buffer pointer											*/
/* Outputs     : Value of 0 - 255, or ERROR								*/
/************************************************************************/
Int16 getByte(char *p, Nat16 radix)
{
	Reg Nat16	low, high;

	if ( ((high = tohex(*p++, radix)) == ERROR) || 
		 ((low = tohex(*p, radix)) == ERROR) )
		return( ERROR );

	return( (high * radix) + low );

} /* getByte() */


/************************************************************************/
/* Function	   : bitsOn													*/
/* Purpose	   : Return how bits set in number							*/
/* Inputs	   : Number to check										*/
/* Outputs	   : Number of bits set										*/
/************************************************************************/
Nat16 bitsOn(Nat16 num)
{
	Reg Nat16	n, bits;

	for ( n = num, bits = 0; n; n >>= 1 )
		if ( n & 1 )
			bits++;

	return( bits );

} /* bitsOn() */


/************************************************************************/
/* Function	   : getSwapWord											*/
/* Purpose	   : Get and swap a word									*/
/* Inputs	   : Word Ptr												*/
/* Outputs	   : Swapped Word											*/
/* Comments	   : Needed because the pointer may be on an odd boundary	*/
/*				 which would cause a bus error							*/
/************************************************************************/
Nat16 getSwapWord(Nat16 *wrdp)
{
	Reg Byte	*p = (Byte *)wrdp;

	return((*p & 0xff) | (p[1] <<8));

} /* getSwapWord() */


/************************************************************************/
/* Function	   : getSwapLong											*/
/* Purpose	   : Get and Swap Bytes in a Long							*/
/* Inputs	   : Long ptr												*/
/* Outputs	   : Swapped Long											*/
/* Comments	   : Needed because the pointer may be on an odd boundary	*/
/*				 which would cause a bus error							*/
/************************************************************************/
Nat32 getSwapLong(Nat32 *p)
{
	Reg Nat16	*wp = (Nat16 *)p;

	return((getSwapWord(wp) & 0xffff) | (getSwapWord(wp+1) << 16));

} /* getSwapLong() */

/* utils.c */
