/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
/* |_o_o|\\ Copyright (c) 1990 The Software Distillery.  All Rights Reserved */
/* |. o.| || This program may not be distributed without the permission of   */
/* | .	| || the authors:			      BBS: (919) 382-8265    */
/* | o	| ||   Dave Baker      Alan Beale	  Jim Cooper		     */
/* |  . |//    Jay Denebeim    Bruce Drake	  Gordon Keener 	     */
/* ======      John Mainwaring Andy Mercier	  Jack Rouse		     */
/*	       John Toebes     Mary Ellen Toebes  Doug Walker  Mike Whitcher */
/* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
   
#include "quickb.h"
#include <exec/memory.h>

static void miniterm(struct XPR_DATA *xd, char *startup);
static long setup_xd(struct XPR_IO *xio);
static void edit_opts(struct XPR_DATA *xd, char *buf);
static void set_optstr(struct XPR_DATA *xd, char *str);

/* protocol termination */
long __saveds __asm XProtocolCleanup(register __a0 struct XPR_IO *xio)
{
	if (xio->xpr_data)
		FreeMem(xio->xpr_data, (long)sizeof(struct XPR_DATA));
	xio->xpr_data = 0;
	return 1L;
}

/* protocol setup */
long __saveds __asm XProtocolSetup(register __a0 struct XPR_IO *xio)
{
	struct XPR_DATA *xd;
	char buf[256];

	if (!setup_xd(xio)) return XPRS_FAILURE;
	xd = (struct XPR_DATA *)xio->xpr_data;
	if (xio->xpr_filename)
	{
		strcpy(buf, xio->xpr_filename);
		set_optstr(xd, buf);
	}
	else
		edit_opts(xd, buf);
	return (xd->optAuto=='Y') ? XPRS_SUCCESS|XPRS_HOSTMON : XPRS_SUCCESS;
}

/* protocol send request */
long __saveds __asm XProtocolSend(register __a0 struct XPR_IO *xio)
{
	if (!setup_xd(xio)) return 0L;
	miniterm((struct XPR_DATA *)xio->xpr_data, "upl/proto:qb");
	return 1L;
}

/* protocol receive request */
long __saveds __asm XProtocolReceive(register __a0 struct XPR_IO *xio)
{
	if (!setup_xd(xio)) return 0L;
	miniterm((struct XPR_DATA *)xio->xpr_data, "dow/proto:qb");
	return 1L;
}

/* look for protocol initiation */
long __saveds __asm XProtocolHostMon(
	register __a0 struct XPR_IO *xio,
	register __a1 char *serbuff,
	register __d0 long actual,
	register __d1 long maxsize)
{
	struct XPR_DATA *xd;
	int c, lastc;

	xd = (struct XPR_DATA *)xio->xpr_data;
	if (!xd) return actual;
	xd->nserbuf = actual;
	xd->serbufp = serbuff;
	while (--xd->nserbuf >= 0)
	{
		lastc = xd->cchar;
		xd->cchar = c = *(xd->serbufp++) & 0x7F;
		if (c == ENQ)
		{
	 		bp_ENQ(xd);
			actual = 0;
		}
		else if (c == DLE)
		{
			bp_DLE(xd);
			actual = 0;
		}
		else if (c == 'I' && lastc == 0x1B)
		{
	 		bp_ESC_I(xd);
			actual = 0;
		}
	}

	return actual;
}

long __saveds __asm XProtocolUserMon(
	register __a0 struct XPR_IO *xio,
	register __a1 char *serbuff,
	register __d0 long actual,
	register __d1 long maxsize)
{
	return actual;
}

/* return failure */
static long Fail()
{
	return 0L;
}

/* write an error message */
void errormsg(struct XPR_DATA *xd, char *msg)
{
	xd->xpru.xpru_updatemask = XPRU_ERRORMSG;
	xd->xpru.xpru_errormsg = msg;
	(*xd->xpr_update)(&xd->xpru);
}

/* set up our data structures */
static long setup_xd(struct XPR_IO *xio)
{
	struct XPR_UPDATE xpru;
	register struct XPR_DATA *xd;

	if (!xio->xpr_data)
	{
		if ((xio->xpr_data = AllocMem((long)sizeof(struct XPR_DATA),
					      MEMF_CLEAR)) == NULL)
		{	/* no memory */
			xpru.xpru_updatemask = XPRU_ERRORMSG;
			xpru.xpru_errormsg = "Out of memory";
			if (xio->xpr_update)
				(*xio->xpr_update)(&xpru);
			return 0L;
		}
		memset(xio->xpr_data, 0, sizeof(struct XPR_DATA));
		xd = (struct XPR_DATA *)xio->xpr_data;
		xd->xio = xio;

		/* default options */
		xd->maxiobuf = 16384;	/* 16K buffer */
		xd->optText = 'H';	/* use CompuServe suggestion */
		xd->optOwrt = 'N';	/* use new file name */
		xd->optAuto = 'Y';	/* activate automatically */
		xd->optDelt = 'N';	/* don't delete after send */
		xd->optKeep = (xio->xpr_extension >= 2L && xio->xpr_unlink)
			    ? 'N'
			    : 'Y';	/* keep partial files */
	}
	else
		xd = (struct XPR_DATA *)xio->xpr_data;

	/* do some initialization */
	xd->Done = xd->Aborting = FALSE;
	xd->nserbuf = 0;
	xd->iobuf = NULL;
	xd->iobufn = 0;

	/* following are optional */
	xd->xpr_update = xio->xpr_update ? xio->xpr_update : Fail;
	xd->xpr_chkabort = xio->xpr_chkabort ? xio->xpr_chkabort : Fail;
	xd->xpr_chkmisc = xio->xpr_chkmisc ? xio->xpr_chkmisc : Fail;
	xd->xpr_gets = xio->xpr_gets ? xio->xpr_gets : Fail;
	xd->xpr_setserial = xio->xpr_setserial;	/* NOTE! no default to fail */
	xd->xpr_finfo = xio->xpr_finfo ? xio->xpr_finfo : Fail;
	xd->xpr_options = (xio->xpr_extension >= 1L)
			  ? xio->xpr_options : NULL;
	xd->xpr_unlink = (xio->xpr_extension >= 2L)
			? xio->xpr_unlink : NULL;

	/* following are required */
	if ((xd->xpr_fopen = xio->xpr_fopen) == NULL)  goto lose;
	if ((xd->xpr_fclose = xio->xpr_fclose) == NULL) goto lose;
	if ((xd->xpr_fread = xio->xpr_fread) == NULL)  goto lose;
	if ((xd->xpr_fwrite = xio->xpr_fwrite) == NULL) goto lose;
	if ((xd->xpr_sread = xio->xpr_sread) == NULL)  goto lose;
	if ((xd->xpr_swrite = xio->xpr_swrite) == NULL) goto lose;

	return 1L;

lose:
	errormsg(xd, "Insufficient XPR protocol support");
	return 0L;
}

/* validate a boolean return value */
static int check_bool(struct XPR_DATA *xd, char *buf, char *which)
{
	if (strcmp(buf, "ON") == 0)
		buf[0] = 'Y';
	else if (strcmp(buf, "OFF") == 0)
		buf[0] = 'N';
	return TRUE;
}

/* allow user to edit option string */
static void edit_opts(struct XPR_DATA *xd, char *buf)
{
	char *p;
	struct xpr_option *optptr[7];
	struct xpr_option opt[7];
	struct {
		char Text[4];
		char Owrt[4];
		char Buff[8];
		char Auto[4];
		char Delt[4];
		char Keep[4];
	}	optbuf;
	register int i;
	long status;
	long l;

	if (!xd->xpr_options)
	{	/* format the current options */
		p = buf;
		*p++ = 'T'; *p++ = xd->optText; *p++ = ',';
		*p++ = 'O'; *p++ = xd->optOwrt; *p++ = ',';
		*p++ = 'B'; p += stcl_d(p, xd->maxiobuf >> 10); *p++ = ',';
		*p++ = 'A'; *p++ = xd->optAuto; *p++ = ',';
		*p++ = 'D'; *p++ = xd->optDelt; *p++ = ',';
		*p++ = 'K'; *p++ = xd->optKeep; *p++ = '\0';

		/* let the user edit it */
		if ((*xd->xpr_gets)("Quick B options:", buf))
		{	/* set the option string */
			set_optstr(xd, buf);
		}
		return;
	}

	/* use the requester interface */
	memset((char *)&optbuf, 0, sizeof(optbuf));
	opt[0].xpro_description = "Quick B options:";
	opt[0].xpro_type = XPRO_HEADER;
	opt[0].xpro_value = NULL;
	opt[0].xpro_length = 0;

	optbuf.Text[0] = xd->optText;
	opt[1].xpro_description = "Text mode (Y,N,H,C):";
	opt[1].xpro_type = XPRO_STRING;
	opt[1].xpro_value = optbuf.Text;
	opt[1].xpro_length = sizeof(optbuf.Text);

	optbuf.Owrt[0] = xd->optOwrt;
	opt[2].xpro_description = "Overwrite mode (Y,N,S):";
	opt[2].xpro_type = XPRO_STRING;
	opt[2].xpro_value = optbuf.Owrt;
	opt[2].xpro_length = sizeof(optbuf.Owrt);

	stcl_d(optbuf.Buff, xd->maxiobuf >> 10);
	opt[3].xpro_description = "I/O buffer size (KB):";
	opt[3].xpro_type = XPRO_LONG;
	opt[3].xpro_value = optbuf.Buff;
	opt[3].xpro_length = sizeof(optbuf.Buff);

	strcpy(optbuf.Auto, (xd->optAuto == 'Y') ? "yes" : "no");
	opt[4].xpro_description = "Auto-activate transfers:";
	opt[4].xpro_type = XPRO_BOOLEAN;
	opt[4].xpro_value = optbuf.Auto;
	opt[4].xpro_length = sizeof(optbuf.Auto);

	strcpy(optbuf.Delt, (xd->optDelt == 'Y') ? "yes" : "no");
	opt[5].xpro_description = "Delete after sending:";
	opt[5].xpro_type = XPRO_BOOLEAN;
	opt[5].xpro_value = optbuf.Delt;
	opt[5].xpro_length = sizeof(optbuf.Delt);

	strcpy(optbuf.Keep, (xd->optKeep == 'Y') ? "yes" : "no");
	opt[6].xpro_description = "Keep partial files:";
	opt[6].xpro_type = XPRO_BOOLEAN;
	opt[6].xpro_value = optbuf.Keep;
	opt[6].xpro_length = sizeof(optbuf.Keep);
	
	/* prompt for options */
	for (i = 0; i < 7; ++i)
		optptr[i] = &opt[i];
	status = (*xd->xpr_options)(7L, optptr);
	if (status <= 0) return;

	/* process options */
	for (i = 1; i < 7; ++i)
	{
		if (!(status & (1 << i))) continue;
		strupr(opt[i].xpro_value);
		switch (i)
		{
		case 1:		/* Text */
			if (strchr("YNCH", optbuf.Text[0]))
				xd->optText = optbuf.Text[0];
			else
				errormsg(xd, "Text option ignored");
			break;
		case 2:		/* Overwrite */
			if (strchr("YNS", optbuf.Owrt[0]))
				xd->optOwrt = optbuf.Owrt[0];
			else
				errormsg(xd, "Overwrite option ignored");
			break;
		case 3:		/* Buffer size */
			stcd_l(optbuf.Buff, &l);
			if (l < 0 || l > 0x7FFFFFFF >> 10)
				errormsg(xd, "Buffer size ignored; out of range");
			else
				xd->maxiobuf = l << 10;
			break;
		case 4:		/* Auto-Activate */
			if (check_bool(xd, optbuf.Auto, "Auto-activate"))
				xd->optAuto = optbuf.Auto[0];
			break;
		case 5:		/* Delete after send */
			if (check_bool(xd, optbuf.Delt, "Delete"))
			{
				if (optbuf.Delt[0] != 'Y' || xd->xpr_unlink)
					xd->optDelt = optbuf.Delt[0];
				else
					errormsg(xd, "Delete option ignored; file deletion not available");
			}
			break;
		case 6:		/* Keep partial files */
			if (check_bool(xd, optbuf.Keep, "Keep"))
			{
				if (optbuf.Keep[0] == 'Y' || xd->xpr_unlink)
					xd->optKeep = optbuf.Keep[0];
				else
					errormsg(xd, "Keep option ignored; file deletion not available");
			}
			break;
		}
	}

	return;
}

/* parse and merge option string with current settings */
static void set_optstr(struct XPR_DATA *xd, char *str)
{
	long l;

	/* convert to upper case for simpler parsing */
	strupr(str);
	for (;;)
	{
		str += strspn(str, " ,");
		switch (*str++)
		{
		case 'T':	/* text mode */
			if (strchr("YNCH", *str))
				xd->optText = *str;
			else
				errormsg(xd, "T option ignored; use TY, TN, TH, or TC");
			break;
		case 'O':	/* overwrite */
			if (strchr("YNS", *str))
				xd->optOwrt = *str;
			else
				errormsg(xd, "O option ignored; use OY, ON, or OS");
			break;
		case 'B':	/* buffer size */
			str += stcd_l(str, &l);
			if (l < 0 || l > 0x7FFFFFFF >> 10)
				errormsg(xd, "Buffer size ignored; out of range");
			else
				xd->maxiobuf = l << 10;
			break;
		case 'A':	/* auto-activate */
			if (strchr("YN", *str))
				xd->optAuto = *str;
			else
				errormsg(xd, "A option ignored; use AY or AN");
			break;
		case 'D':	/* delete after send */
			if (!strchr("YN", *str))
				errormsg(xd, "D option ignored; use DY or DN");
			else if (*str == 'Y' && !xd->xpr_unlink)
				errormsg(xd, "DY option ignored; file deletion not available");
			else
				xd->optDelt = *str;
			break;
		case 'K':	/* keep partial receives */
			if (strchr("YN", *str))
				xd->optKeep = *str;
			else
				errormsg(xd, "K option ignored; use KY or KN");
			break;
		default:
			return;
		}

		/* skip rest of option */
		str += strcspn(str, " ,");
	}
}

void cputc(struct XPR_DATA *xd, int c)
{
   char ch = c;
   (*xd->xpr_swrite)(&ch, 1L);
}

/* called by cgetc macro to refill the buffer */
int cfilbuf(struct XPR_DATA *xd, long timeout)
{
	long n;
	n = (*xd->xpr_sread)(xd->serbuf, (long)MAX_SERBUF, 0L);
	if (n <= 0)
		n = (*xd->xpr_sread)(xd->serbuf, 1L, timeout);
	if (n <= 0)
		return -1;
	xd->nserbuf = n-1;
	xd->serbufp = &xd->serbuf[1];
	return (int)xd->serbuf[0];
}

static void miniterm(struct XPR_DATA *xd, char *startup)
{
   int c = -1;
   int lastc;
   char buf[256];
   char prompt[1024];
   char *p;
   int i;
   int to;

   /* get the startup line */
   strcpy(buf, startup);
   if ((*xd->xpr_gets)("Startup line", buf))
   {
      for (p = buf; *p; ++p)
         cputc(xd, *p);
      cputc(xd, '\r');
   }

   xd->xpru.xpru_updatemask = XPRU_MSG;
   xd->xpru.xpru_msg = "Entering protocol";
   (*xd->xpr_update)(&xd->xpru);

   i = 0;
   to = 0;
   do {
      lastc = c;
      c = cgetc(xd, 1000000L);
      if (c < 0)
      {  /* timed out */
         if (++to == 10)
            break;
	 if (to == 1 && i > 0)
	 {
	    prompt[i] = 0;
	    buf[0] = 0;
	    strcpy(buf, xd->xio->xpr_filename);
            if ((*xd->xpr_gets)(prompt, buf))
	    {
	       for (p = buf; *p; ++p)
	          cputc(xd, *p);
      	       cputc(xd, '\r');
	    }
	    i = 0;
	 }
      }
      else
      {
      	 to = 0;

	 if (c == ENQ)
	    bp_ENQ(xd);
         else if (c == DLE)
            bp_DLE(xd);
         else if (c == 'I' && lastc == 0x1B)
            bp_ESC_I(xd);
	 else if (c == '\n')
	 {
	    prompt[i] = 0;
	    xd->xpru.xpru_updatemask = XPRU_MSG;
	    xd->xpru.xpru_msg = prompt;
	    (*xd->xpr_update)(&xd->xpru);
	    i = 0;
         }
         else if (c != '\r' && i < sizeof(prompt)-1)
            prompt[i++] = c;
      }
      if ((*xd->xpr_chkabort)())
         xd->Aborting = TRUE;
   }  while (!xd->Done && !xd->Aborting);

   xd->xpru.xpru_updatemask = XPRU_MSG;
   xd->xpru.xpru_msg = "Timed out";
   if (!xd->Done && !xd->Aborting)
      (*xd->xpr_update)(&xd->xpru);

   /* reset last character so HostMon isn't confused */
   xd->cchar = -1;
}

/* open buffered file */
long bfopen(struct XPR_DATA *xd, char *filename, char *mode)
{
	long file;
	file = (*xd->xpr_fopen)(filename, mode);
	if (!file) return file;
	if (xd->maxiobuf)
	{
		xd->iobuf = AllocMem(xd->maxiobuf, 0L);
		if (xd->iobuf == NULL)
			errormsg(xd, "Not enough memory to buffer I/O");
	}
	if (!xd->iobuf || mode[0] == 'r')
	{
		xd->iobufn = 0;			/* bytes available */
		xd->writing = FALSE;
	}
	else
	{
		xd->iobufn = 0;			/* bytes used */
		xd->writing = TRUE;
	}
	xd->iobufp = xd->iobuf;
	return file;
}

/* buffered file close */
long bfclose(struct XPR_DATA *xd, long file)
{
	if (xd->writing && xd->iobufn)
		(*xd->xpr_fwrite)(xd->iobuf, xd->iobufn, 1L, file);
	if (xd->iobuf)
		FreeMem(xd->iobuf, xd->maxiobuf);
	xd->iobuf = NULL;
	return (*xd->xpr_fclose)(file);
}

/* buffered file read */
long bfread(struct XPR_DATA *xd, char *buf, long len, long file)
{
	register long n;
	register long remain;
	if (xd->iobuf == NULL)
		return (*xd->xpr_fread)(buf, 1L, 1*len, file);
	remain = len;
	while (remain > 0)
	{
		if (xd->iobufn <= 0)
		{	/* refill buffer */
			xd->iobufn = (*xd->xpr_fread)(xd->iobuf, 1L,
						xd->maxiobuf, file);
			if (xd->iobufn <= 0)
				return len - remain;
			xd->iobufp = xd->iobuf;
		}
		n = min(xd->iobufn, remain);
		memcpy(buf, xd->iobufp, (unsigned)n);
		xd->iobufp += n;
		xd->iobufn -= n;
		buf += n;
		remain -= n;
	}
	return len;
}

/* buffered file read, with optional text processing */
long xbfread(struct XPR_DATA *xd, char *buf, long len, long file)
{
	unsigned char c;
	long n;
	if (!xd->TextMode)
		return bfread(xd, buf, len, file);
	for (n = 0; n < len; ++n)
	{
		if (xd->TextC >= 0)
		{
			buf[n] = xd->TextC;
			xd->TextC = -1;
		}
		else if (bfread(xd, (char *)&c, 1L, file) != 1)
			break;
		else if (c == LF)
		{
			buf[n] = CR;
			xd->TextC = LF;
		}
		else
			buf[n] = c;
	}

	return n;
}

/* buffered file write */
long bfwrite(struct XPR_DATA *xd, char *buf, long len, long file)
{
	register long n;
	register long remain;
	if (xd->iobuf == NULL)
		return (*xd->xpr_fwrite)(buf, 1L, 1*len, file);
	remain = len;
	for (;;)
	{
		if (xd->iobufn >= xd->maxiobuf)
		{	/* flush buffer */
			n = (*xd->xpr_fwrite)(xd->iobuf, xd->iobufn, 1L, file);
			if (n != 1)
				return -1;
			xd->iobufn = 0;
			xd->iobufp = xd->iobuf;
		}

		if (remain <= 0) break;

		n = xd->maxiobuf - xd->iobufn;
		if (n > remain) n = remain;
		memcpy(xd->iobufp, buf, (unsigned)n);
		xd->iobufp += n;
		xd->iobufn += n;
		buf += n;
		remain -= n;
	}
	return len;
}

/* buffered file write with optional text processing */
long xbfwrite(struct XPR_DATA *xd, char *buf, long len, long file)
{
	register int i;
	if (!xd->TextMode)
		return bfwrite(xd, buf, len, file);
	if (xd->TextC == CPMEOF)
		return len;
	for (i = 0; i < len; ++i)
	{
		if (xd->TextC == CR && buf[i] != LF)
		{	/* carriage return not part of CR-LF */
			if (bfwrite(xd, "\n", 1, file) != 1)
				return i;
		}
		xd->TextC = buf[i];
		if (buf[i] == CPMEOF)
			break;
		if (buf[i] != CR && bfwrite(xd, &buf[i], 1, file) != 1)
			return i;
	}
	return len;
}
