/**********************************************************************
 * Utils.c: Miscellaneous support routines for xprzmodem.library;
 * Version 2.10, 12 February 1991, by Rick Huebner.
 * Released to the Public Domain; do as you like with this code.
 *
 * Version 2.50, 15 November 1991, by William M. Perkins.  Added code
 * to update_rate() function in utils.c to avoid the Guru # 80000005
 * you would have gotten if you had decided to adjust the system clock
 * back during an upload or download.
 *
 * Mysprintf() function to replace sprintf() and proto code to use 
 * libinit.o and linent.o library code was supplied by Jim Cooper of SAS.
 *
 **********************************************************************/

/*#include <proto/all.h>
#include <exec/types.h>
#include <exec/memory.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dos.h>*/

#include "microdot.h"

#include "xproto.h"
#include "zmodem.h"
#include "xprzmodem.h"

#include "asyncio.h"

/* Transfer options to use if XProtocolSetup not called */
struct SetupVars Default_Config =
{
   NULL, NULL, 0,
   { "N" }, { "R" }, { "16" }, { "0" }, { "20" },
   { "N" }, { "N" }, { "Y" }, { "N" }, { "Y" }, { "" }
   };

#ifdef DEBUGLOG
UBYTE DebugName[] = "T:XDebug.Log";
void *DebugLog = NULL;
#endif

/**********************************************************
 *	long XProtocolCleanup(struct XPR_IO *xio)
 *
 * Called by comm program to give us a chance to clean
 * up before program ends
 **********************************************************/
long XProtocolCleanup(struct XPR_IO *xio)
{
   /* Release option memory, if any */
   if (xio->xpr_data)
   {
      FreeMem(xio->xpr_data, (long) sizeof(struct SetupVars));
      xio->xpr_data = NULL;
      }

   return XPRS_SUCCESS;
   }	/* End of long XProtocolCleanup() */

/**********************************************************
 *	struct Vars *setup(struct XPR_IO *io)
 *
 * Perform setup and initializations common to both Send
 * and Receive routines
 **********************************************************/
struct Vars *setup(struct XPR_IO *io)
{
/*   static long bauds[] = { 110, 300, 1200, 2400, 4800, 9600, 19200, 31250,
			   38400, 57600, 76800, 115200 };*/
   struct SetupVars *sv;
   struct Vars *v;
#ifdef DEBUGLOG
   long i, *lng;
#endif

   /* Hook in default transfer options if XProtocolSetup wasn't called */
   if (! (sv = (void *) io->xpr_data))
   {
      io->xpr_data = AllocMem((long) sizeof(struct SetupVars), MEMF_CLEAR);
      if (! (sv = (void *) io->xpr_data))
      {
	 ioerr(io, "Kein Speicher!");
	 return NULL;
	 }
      *sv = Default_Config;
      }

   /* Allocate memory for our unshared variables, to provide reentrancy */
   if (! (v = AllocMem((long) sizeof(struct Vars), MEMF_CLEAR)))
   {
nomem:
      ioerr(io, "Kein Speicher!");
      return NULL;
      }
   v->Modemchar = v->Modembuf;

   /* If framelength was intended to match buffer size, stay in sync */
/*   v->Tframlen = atol(sv->option_f);
   if (v->Tframlen && v->Tframlen == origbuf)
      v->Tframlen = v->Filebufmax;*/

   v->ErrorLimit = atol(sv->option_e);

   /* Copy caller's io struct into our Vars for easier passing */
   v->io = *io;

#ifdef DEBUGLOG
   if (! DebugLog)
      DebugLog = (*v->io.xpr_fopen)(DebugName, "w");
   dlog(v, "XPR_IO struct:\n");
   for (i = 0, lng = (long *) io; i < sizeof(struct XPR_IO) / 4; ++i)
   {
      mysprintf(v->Msgbuf, "  %08lx\n", *lng++);
      dlog(v, v->Msgbuf);
      }
#endif

   /* Get baud rate; set serial port mode if necessary (and possible) */
      v->Baud = 38400;

   return v;
   }	/* End of struct Vars *setup() */

/**********************************************************
 *	void set_textmode(struct Vars *v)
 *
 * Set text/binary mode flags in accordance with T option
 * setting
 **********************************************************/
void set_textmode(struct Vars *v)
{
      v->Rxascii = FALSE;
      v->Rxbinary = TRUE;
      v->Lzconv = ZCBIN;
   }	/* End of void set_textmode() */

/**********************************************************
 *	UBYTE *find_option(UBYTE *buf, UBYTE option)
 *
 * Search for specified option setting in string
 **********************************************************/
UBYTE *find_option(UBYTE *buf, UBYTE option)
{
   while (*buf)
   {
      buf += strspn(buf, " ,\t\r\n");
      if (*buf == option)
	 return ++buf;
      buf += strcspn(buf, " ,\t\r\n");
      }

   return NULL;
   }	/* End of UBYTE *find_option() */

/**********************************************************
 *	void canit(struct Vars *v)
 *
 * send cancel string to get the other end to shut up
 **********************************************************/
void canit(struct Vars *v)
{
   static char canistr[] = { 24, 24, 24, 24, 24, 24, 24, 24, 24, 24,
			      8,  8,  8,  8,  8,  8,  8,  8,  8,  8, 0 };

   zmputs(v, canistr);
   }	/* End of void canit() */

/**********************************************************
 *	void zmputs(struct Vars *v, UBYTE *s)
 *
 * Send a string to the modem, with processing for \336 (sleep 1 sec)
 * and \335 (break signal, ignored since XPR spec doesn't support it)
 **********************************************************/
void zmputs(struct Vars *v, UBYTE *s)
{
   UBYTE c;

   while (*s)
   {
      switch (c = *s++)
      {
      case '\336':
	 Delay(50L);
      case '\335':
	 break;
      default:
	 sendline(v,c);
         }
      }
   sendbuf(v);
   }	/* End of void zmputs() */

/**********************************************************
 *	void xsendline(struct Vars *v, UBYTE c)
 *
 * Write one character to the modem
 **********************************************************/
void xsendline(struct Vars *v, UBYTE c)
{
   v->Outbuf[v->Outbuflen++] = c;
   if (v->Outbuflen >= sizeof(v->Outbuf))
      sendbuf(v);
   }	/* End of void xsendline() */

/**********************************************************
 *	void sendbuf(struct Vars *v)
 *
 * Send any data waiting in modem output buffer
 **********************************************************/
void sendbuf(struct Vars *v)
{
   if (v->Outbuflen)
   {
      xpr_swrite(v->Outbuf, (long) v->Outbuflen);
      v->Outbuflen = 0;
      }
   }	/* End of void sendbuf() */

/**********************************************************
 *	short readock(struct Vars *v, short tenths)
 *
 * Get a byte from the modem;
 * return TIMEOUT if no read within timeout tenths of a
 * second, return RCDO if carrier lost or other fatal error
 * (sread returns -1).  Added in some buffering so we
 * wouldn't hammer the system with single-byte serial port
 * reads.  Also, the buffering makes char_avail() a lot
 * easier to implement.
 **********************************************************/
short readock(struct Vars *v, short tenths)
{
   long t;

   /* If there's data waiting in our buffer, return next byte */
   if (v->Modemcount)
   {
gotdata:
      --v->Modemcount;
      return (short) (*v->Modemchar++);
      }
   /*
    * Our buffer is empty; see if there's anything waiting in system buffer.
    * If the caller is in a hurry, don't wait around, but if it can spare
    * a half second, wait a bit and build up some input so we don't do as
    * many sread() calls.
    */
   t = (tenths < 5) ? 0 : 500000;
#ifdef DEBUGLOG
   mysprintf(v->Msgbuf,
      "Input buffer empty; calling sread for %ld bytes, %ld usec\n",
      (long) sizeof(v->Modembuf), t);
   dlog(v, v->Msgbuf);
#endif
   v->Modemcount = xpr_sread(v->Modembuf, (long) sizeof(v->Modembuf),
      t);
#ifdef DEBUGLOG
   mysprintf(v->Msgbuf, "   sread returned %ld\n", v->Modemcount);
   dlog(v, v->Msgbuf);
#endif
   if (v->Modemcount < 0)   /* Carrier dropped or other fatal error; abort */
   {
      v->Modemcount = 0;
      return RCDO;
      }
   else if (! v->Modemcount)    /* Nothing in system buffer; try waiting */
   {
      t = tenths * 100000L - t;
#ifdef DEBUGLOG
      mysprintf(v->Msgbuf, "   calling sread for 1 byte, %ld usec\n", t);
      dlog(v, v->Msgbuf);
#endif
      v->Modemcount = xpr_sread(v->Modembuf, 1L, t);
#ifdef DEBUGLOG
      mysprintf(v->Msgbuf, "   sread returned %ld\n", v->Modemcount);
      dlog(v, v->Msgbuf);
#endif
      if (v->Modemcount < 0)
      {
	 v->Modemcount = 0;
	 return RCDO;
         }
      else if (! v->Modemcount)  /* Nothing received in time */
	 return TIMEOUT;
      }
   v->Modemchar = v->Modembuf;   /* Reset buffer pointer to start of data */
   goto gotdata;
   }	/* End of short readock() */

/**********************************************************
 *	char char_avail(struct Vars *v)
 *
 * Check if there's anything available to read from the
 * modem
 **********************************************************/
char char_avail(struct Vars *v)
{
   if (v->Modemcount)
      return TRUE;

   /* No data in our buffer; check system's input buffer */
   v->Modemcount = xpr_sread(v->Modembuf, (long) sizeof(v->Modembuf), 0L);
   if (v->Modemcount < 1)	/* Nothing in system buffer either */
   {
      v->Modemcount = 0;
      return FALSE;
      }
   else				/* System buffer had something waiting for us */
   {
      v->Modemchar = v->Modembuf;
      return TRUE;
      }
   }	/* End of char char_avail() */

/**********************************************************
 *	void update_rate(struct Vars *v)
 *
 * Update the elapsed time, expected total time, and
 * effective data transfer rate values for status display
 **********************************************************/
void update_rate(struct Vars *v)
{
   ULONG sent, elapsed, expect;
   short hr, min;
   struct timeval tv;

   /* Compute effective data rate so far in characters per second */
   sent = v->xpru.xpru_bytes - v->Strtpos;
   getsystime(&tv);
   elapsed = (tv.tv_secs & 0x7FFFFF) * 128 + tv.tv_micro / 8192;
   elapsed -= (v->Starttime.tv_secs & 0x7FFFFF) * 128
      + v->Starttime.tv_micro / 8192;
   if (elapsed < 128 || elapsed > 0x7FFFFF)
   /*			^^^^^^^^^^^^^^^^^^ Kludge for the GURU!  -WMP- */
      elapsed = 128;
   /*
    * If we haven't transferred anything yet (just starting), make reasonable
    * guess (95% throughput); otherwise, compute actual effective transfer
    * rate
    */
   v->xpru.xpru_datarate = (sent) ? (sent * 128 / elapsed)
      : (v->Baud * 95 / 1000);
   if(v->xpru.xpru_datarate<1) v->xpru.xpru_datarate=1;
   /* Compute expected total transfer time based on data rate so far */
   if (v->xpru.xpru_filesize < 0)
      expect = 0;		/* Don't know filesize; display time=0 */
   else
      expect = (v->xpru.xpru_filesize - v->Strtpos) / v->xpru.xpru_datarate;
   hr = expect / 3600;		/* How many whole hours */
   expect -= hr * 3600;		/* Remainder not counting hours */
   min = expect / 60;		/* How many whole minutes */
   expect -= min * 60;		/* Remaining seconds */
   mysprintf(v->Msgbuf, "%02ld:%02ld:%02ld", (long) hr, (long) min, expect);
   v->xpru.xpru_expecttime = (char *) v->Msgbuf;

   /* Compute elapsed time for this transfer so far */
   elapsed /= 128;
   hr = elapsed / 3600;
   elapsed -= hr * 3600;
   min = elapsed / 60;
   elapsed -= min * 60;
   mysprintf(v->Msgbuf + 20, "%02ld:%02ld:%02ld", (long) hr, (long) min,
      elapsed);
   v->xpru.xpru_elapsedtime = (char *) v->Msgbuf + 20;
   }	/* End of void update_rate() */

/**********************************************************
 *	long bfopen(struct Vars *v, UBYTE *mode)
 *
 * Buffered file I/O fopen() interface routine
 **********************************************************/
extern BOOL nowdoicons;
long bfopen(struct Vars *v, UBYTE *mode)
{
	int om;

	switch(*mode) {
		case 'A':
		case 'a': om=MODE_APPEND; break;
		case 'w':
		case 'W': om=MODE_WRITE; break;
		default: om=MODE_READ; break;
	}

	if( nowdoicons && ( om == MODE_WRITE || om == MODE_APPEND ) )
		createdataicon( v->Filename, prefs.dl_defaulttool );

	return( ( long )OpenAsync( v->Filename,om, max( 32, prefs.zmodembuffer ) * 1024 ) );

}	/* End of long bfopen() */

/**********************************************************
 *	void bfclose(struct Vars *v)
 *
 * Buffered file I/O fclose() interface routine
 **********************************************************/
void bfclose(struct Vars *v)
{
   if (v->File) {
		CloseAsync((struct AsyncFile*)v->File);
		v->File=NULL;
   }
}	/* End of void bfclose() */

/**********************************************************
 *	void bfseek(struct Vars *v, long pos)
 *
 * Buffered file I/O fseek() interface routine
 **********************************************************/
void bfseek(struct Vars *v, long pos)
{
	SeekAsync((struct AsyncFile*)v->File,pos,MODE_START);
   }	/* End of void bfseek() */

/**********************************************************
 *	long bfread(struct Vars *v, UBYTE *buf, long length)
 *
 * Buffered file I/O fread() interface routine
 **********************************************************/
long bfread(struct Vars *v, UBYTE *buf, long length)
{
	return(ReadAsync((struct AsyncFile*)v->File,buf,length));
}	/* End of long bfread() */

/**********************************************************
 *	long bfwrite(struct Vars *v, UBYTE *buf, long length)
 *
 * Buffered file I/O fwrite() interface routine
 **********************************************************/
long bfwrite(struct Vars *v, UBYTE *buf, long length)
{
	return(WriteAsync((struct AsyncFile*)v->File,buf,length));

}	/* End of long bfwrite() */

/**********************************************************
 *	void ioerr(struct XPR_IO *io, char *msg)
 *
 * Have the comm program display an error message for us,
 * using a temporary XPR_UPDATE structure; used to display
 * errors before Vars gets allocated
 **********************************************************/
void ioerr(struct XPR_IO *io, char *msg)
{
   struct XPR_UPDATE xpru;

      xpru.xpru_updatemask = XPRU_ERRORMSG;
      xpru.xpru_errormsg = msg;
      xpr_update(&xpru);
   }	/* End of void ioerr() */

/**********************************************************
 *	void upderr(struct Vars *v, char *msg)
 *
 * Have the comm program display an error message for us, using the
 * normal XPR_IO structure allocated in Vars
 **********************************************************/
void upderr(struct Vars *v, char *msg)
{
   v->xpru.xpru_updatemask = XPRU_ERRORMSG;
   v->xpru.xpru_errormsg = msg;
   if (msg == v->Msgbuf)	/* Ensure message length < 50 */
      msg[48] = '\0';
   xpr_update(&v->xpru);
#ifdef DEBUGLOG
   dlog(v, msg);
   dlog(v, "\n");
#endif
   }	/* End of void upderr() */

/**********************************************************
 *	void updmsg(struct Vars *v,char *msg)
 *
 * Have the comm program display a normal message for us
 **********************************************************/
void updmsg(struct Vars *v,char *msg)
{
   v->xpru.xpru_updatemask = XPRU_MSG;
   v->xpru.xpru_msg = msg;
   if (msg == v->Msgbuf)	/* Ensure message length < 50 */
      msg[48] = '\0';
   xpr_update(&v->xpru);
#ifdef DEBUGLOG
   dlog(v, msg);
   dlog(v, "\n");
#endif
   }	/* End of void updmsg() */

/**********************************************************
 *	char exist(struct Vars *v)
 *
 * Check whether file already exists; used to detect
 * potential overwrites
 **********************************************************/
char exist(struct Vars *v)
{
	BPTR f;

	f=Lock(v->Filename,SHARED_LOCK);
	if(f) { UnLock(f); return(TRUE); }
	else return(FALSE);
}	/* End of char exist() */

ULONG UnixTimeOffset = 252482400;

/**********************************************************
 *	ULONG getsystime(struct timeval *tv)
 *
 *      This function was rewritten using DateStamp() to
 * eliminate the opening and closing of the timer.device
 * that was occurring everytime this function was called.
 * An attempt to save some processing time.   -WMP-
 **********************************************************/
ULONG getsystime(struct timeval *tv)
{
   struct DateStamp ds;
   ULONG  secs;

   DateStamp(&ds);

   secs = (ds.ds_Days * 86400) + (ds.ds_Minute * 60)
      + (ds.ds_Tick / TICKS_PER_SECOND);
   if (tv)
   {
      tv->tv_secs = secs;
      tv->tv_micro = 0;		/* Not Used. */
      }

   return secs;
   }	/* End of ULONG getsystime() */
