/*
 *  k e r p o s s y s  -  KERMIT routines for P/OS
 *
 *  Communications I/O package for KERMIT/POS using the
 *  XK0: port.  Uses the RSX/POS system services library
 *  CX.OLB.  Also contains user interface and message board
 *  routines.
 *  
 *  By:
 *	Bob Denny
 *	Alisa Systems, Inc.
 *	Pasadena, CA  91101
 *	07-Jan-84
 *
 *  Modification history:
 *
 *  09-Jan-84 RBD	Added ps_fil, ps_pmsg and ps_usage in an attempt
 *			to make KERFIL.C more portable & access P/OS
 *			menu-moron services.
 *  12-Jan-84 RBD	Add ps_xfil to set up list of remote filespecs
 *			to send to server for transmission back to us.
 *			Decided to make up my own 'menu' ... too bad.
 *			Required a lot of terminal fiddling!
 *  13-Jan-83 RBD	Move terminal setup and restore into system
 *			intialization and restore routines, so terminal
 *			runs in our flavor all of the time.  Also,
 *			clear the screen on exit.
 */

#include <cx.h>
#include <qioxk.h>
#include <qiottd.h>

#define DEBUGIT

#define TRUE 1
#define FALSE 0

/*
 * We will use LUN 6 and EFN 6 for XK: QIO's
 * leaving room for 2 open files.
 */
#define COMM_DEVICE	"XK:"		/* XK0: is used by KERMIT */
#define COMM_UNIT	0
#define COMM_LUN    	6
#define	COMM_EFN	6
#define RBUF_SIZE	256		/* Local receive buffer size */

/*
 * Characteristics tables used by SF.GMC and
 * SF.SMC in saving, setting and restoring
 * the XK: port.
 */

/*
 * Symbolic array indexes to values
 */
#define ARC	1
#define BIN	3
#define EPA	5
#define FSZ	7
#define PAR	9
#define RSP	11
#define STB	13
#define XSP	15
#define EBC	17
#define MTP	19

static byte saved_chars[] =		/* Saved XK: characteristics */
	{
	TC_ARC,	0,
	TC_BIN,	0,
	TC_EPA,	0,
	TC_FSZ,	0,
	TC_PAR,	0,
	TC_RSP, 0,
	TC_STB, 0,
	TC_XSP, 0,
	TC_8BC, 0,
	XT_MTP, 0
	};
static word savpar[] = 
	{saved_chars, sizeof(saved_chars)};
        
static byte kermit_chars[] =		/* KERMIT's XK: characteristics */
	{
	TC_ARC,	1,			/* Answer after 1 ring (remote) */
	TC_BIN,	0,			/* Enable ^S/^Q flow control */
	TC_EPA,	1,			/* (not used, even parity) */
	TC_FSZ,	8,			/* 8-bit character width */
	TC_PAR,	0,			/* No parity */
	TC_RSP, 0,			/* Rec baud rate (set at run time) */
	TC_STB, 1,			/* 1 stop bit */
	TC_XSP, 0,			/* Xmt baud rate (set at run time) */
	TC_8BC, 1,			/* Pass 8-bit characters */
	XT_MTP, XTM_NO			/* Hardwired line (no modem) */
	};
static word setpar[] = 			/* QIO parameter list */
	{kermit_chars, sizeof(kermit_chars)};

/*
 * Local storage & data for receiving
 */
static byte rbuf[RBUF_SIZE];		/* Receive buffer */
static byte *rbp = rbuf;		/* Receive buffer scan pointer */
static word nchars = 0;			/* Number of char's remaining */
static word rpar[6] = 			/* QIO parameters for read */
	{0, 0, 0, 0, 0, 0};

static int iosb[2];			/* I/O status block */

/*
 * File selection menu data
 */
#define MAXCHOICE 40			/* Max choices from oldfil */
#define MAXSELECT 10			/* Max specify 10 remote files */
static char filspecs[MAXCHOICE][50];	/* Array of returned filespecs */
static char *filvec[MAXCHOICE];		/* Array of filespec pointers */

/*
 * Terminal characteristics control data & abort
 * AST stuff.
 */
static char *clrseq = "\033[1;1H\033[J";	/* Erase screen sequence */
extern int astsrv();			/* Declare our ^C AST service */
static word astpar[] = {0, 0 ,astsrv, 0, 0, 0};
static word aboarm = FALSE;		/* TRUE = ^C aborts transfer */
static word was_esc;			/* Remember original /[NO]ESCAPE */
static byte escchr[] = {TC_ESQ, 0};
static word escpar[] = {escchr, 2, 0, 0, 0, 0};

extern int $dsw;			/* P/OS directive status */
extern int debug;			/* Required KERFIL variables */
extern int remote;			/* TRUE = we are in remote */
extern char **filelist;			/* --> list of filespec strings */
extern int filecount;			/* Number of filespecs left to do */
extern int aboflag;			/* Transfer abort flag */
extern char *stdout;			/* No need for <stdio> */

/*
 * xk_set - Save current XK: settings and change them 
 *	    to suit KERMIT.  Also set up terminal for
 *	    our manual QIO'ing with escape sequence parsing.
 *
 * Exits after printing an error message if there is
 * some problem.
 */
xk_set(speed)
word speed;				/* Baud rate (numeric) */
   {
   byte scode;

   /*
    * Assign and attach the port
    */
   if(alunx(COMM_LUN, COMM_DEVICE, COMM_UNIT) != IS_SUC)
      xerr("Device assignment failed", $dsw);
   if(qiow(IO_ATT, COMM_LUN, COMM_EFN, iosb, 0, 0) != IS_SUC)
      xerr("Attach rejected", $dsw);
   if(iosb[0] != IS_SUC)
      xerr("Failed to attach port", iosb[0]);

   /*
    * Save current characteristics and set up
    * those required by KERMIT.  No error checks
    * here ...
    */
   qiow(SF_GMC, COMM_LUN, COMM_EFN, 0, 0, savpar);  /* Save current chars */
#ifdef DEBUGIT
   if($dsw != IS_SUC)
      xerr("GMC rejected", $dsw);
   if(iosb[0] != IS_SUC)
      xerr("GMC failed", iosb[0]);
#endif
   switch(speed)			/* Translate speed to QIO code */
      {
      case  300:
         scode = S_300; 
         break;
      case 1200:
         scode = S_1200;
         break;
      case 2400:
         scode = S_2400;
         break;
      case 4800:
         scode = S_4800;
         break;
      case 9600:
         scode = S_9600;
         break;
      case 19200:
         scode = S_19_2;
         break;
      default:
         xerr("Bad baud rate");
      }
   kermit_chars[RSP] = kermit_chars[XSP] = scode;  /* Fill in new speed */

   qiow(SF_SMC, COMM_LUN, COMM_EFN, iosb, 0, setpar); /* Set KERMIT's chars */
#ifdef DEBUGIT
   if($dsw != IS_SUC)
      xerr("SMC rejected", $dsw);
   if(iosb[0] != IS_SUC)
      xerr("SMC failed", iosb[0]);
#endif

   /*
    * Finally, attach & set up the terminal for escape sequence 
    * processing and ^C AST.  The ^C (INTERRUPT-DO) will be used to
    * set the abort flag the first time, and the second time, it'll
    * abort the program itself.
    */
   qiow(SF_GMC, 1, 1, 0, 0, escpar);	/* Get current terminal setting */
   if((was_esc = (word)escchr[1]) == 0)	/* If not /ESCAPE */
      {					
      escchr[1] = 1;
      qiow(SF_SMC, 1, 1, 0, 0, escpar);	/* SET TERM/ESCAPE */
      }
   qiow(IO_DET, 1, 1, 0, 0, 0);		/* Do I hafta? ... Just to  */
   qiow(IO_ATA|TF_ESQ, 1, 1, 0, 0, astpar);	/* Ask for ^C & escape */

   }


/*
 * xk_rst - Flush, restore & detach XK: port,
 *	    clear screen & detach TI:
 */
xk_rst()
   {

   xk_flu();				/* Flush XK: input buffer */
   qiow(SF_SMC, COMM_LUN, COMM_EFN, iosb, 0, savpar); /* Restore char's */
   qiow(IO_DET, COMM_LUN, COMM_EFN, 0, 0, 0);	/* Detach the line */
   /*
    * Restore former setting of /[NO]ESCAPE
    */
   if(!was_esc)
      {
      escchr[1] = 0;
      qiow(SF_SMC, 1, 1, 0, 0, escpar);
      }
   printf(clrseq); fflush(stdout);
   qiow(IO_DET, 1, 1, 0, 0, 0);		/* Detach terminal */
   }

/*
 * xk_flu - Flush all input buffers
 */
static byte fluchr[] = {TC_TBF, 1};
static word flupar[] = {fluchr, sizeof(fluchr), 0, 0, 0, 0};

xk_flu()
   {
   rbp = rbuf;				/* Flush local buffer */
   nchars = 0;
   qiow(SF_SMC, COMM_LUN, COMM_EFN, 0, 0, flupar);  /* Flush XK: buffer */
   }

/*
 * xk_wrt - Write a string of characters to XT: port
 *
 * No error checking ...
 */
static word wrtpar[] = {0, 0, 0, 0, 0, 0};

xk_wrt(buf, bytes)
char *buf;				/* Buffer address */
word bytes;				/* Byte count */
   {
   wrtpar[0] = buf;			/* Set up parameters */
   wrtpar[1] = bytes;
   qiow(IO_WLB, COMM_LUN, COMM_EFN, iosb, 0, wrtpar);
   }

/*
 * xk_rch - Return a received character
 *
 * I find it amazing that this is so easy ...
 */
static word rlbpar[] = {rbuf, 0, 0, 0, 0, 0};

xk_rch(cp, timeout)
char *cp;				/* --> place to store character */
word timeout;				/* Timeout value, seconds */
   {
again:
   if(nchars--)				/* If char(s) remaining locally */
      {
      *cp = *rbp++;			/* Return one */
      return(TRUE);			/* Success */
      }

   rbp = rbuf;				/* Reset scan pointer */

   rlbpar[1] = RBUF_SIZE;		/* Get everything in XT: buffer */
   rlbpar[2] = 0;			/* (timeout=0) */
   qiow(IO_RLB|TF_TMO, COMM_LUN, COMM_EFN, iosb, 0, rlbpar);
   if(nchars = iosb[1])			/* If we got something */
      goto again;			/* Go back and fetch a character */

   rlbpar[1] = 1;			/* Wait till a character arrives */
   rlbpar[2] = 256 * timeout;		/* Hang a timed-out read (sec) */
   qiow(IO_RLB|TF_TMO, COMM_LUN, COMM_EFN, iosb, 0, rlbpar);
   if(nchars = iosb[1])			/* If we got something */
      goto again;			/* Go back and fetch a character */
   return(FALSE);			/* ERROR - Timed out */
   }

/*
 * xerr - Handle fatal initialization errors
 */
static xerr(msg, code)
char *msg;
word code;
   {
   char *fmt1, *fmt2;
   char mb[86];

   fmt1 = "KERMIT/POS Initialization error:\n";
   fmt2 = "%s  Code = %d\n";
   sprintf(mb, fmt2, msg, code);

   ps_pmsg(fmt1);
   ps_pmsg(mb);
   p$fatler(mb);
   }

/*
 * ps_usage - Inform user of command line error & exit
 */
ps_usage()
   {
   char *m;
 
   m = "KERFIL command line error";
   ps_pmsg(m);
   p$fatler(m);
   }

/*
 * ps_pmsg - Print message to local user
 */
ps_pmsg(fmt, a1, a2, a3, a4, a5)
char *fmt;
   {
   char msgbuf[86];
   char *cp;

   if(remote)				/* Do nothing if remote */
      return;

   printf(fmt, a1, a2, a3, a4, a5);	/* Display message */
   printf("\n");
   cp = sprintf(msgbuf, "Kermit: ");	/* Format for message board */
   sprintf(cp, fmt, a1, a2, a3, a4, a5);
   p$msgbrd(msgbuf, iosb);		/* Write to message board */
#ifdef DEBUGIT
   if(iosb[0] < 0)
      printf("Message board send failed.  stat[1] = %d\n", iosb[1]);
#endif
   }

/*
 * ps_gfil - Get a list of local filespecs from the user
 */
ps_gfil()
   {
   int nch;				/* Returns with # chosen */
   int stat[2];
   register int i;

   for(i=0; i<MAXCHOICE; i++)		/* Initialize file spec vector */
      filvec[i] = &filspecs[i][0];

   nch = MAXCHOICE;
   p$oldfil("*.*", &nch, filspecs,	/* Ask for file(s) */
      "Select the file(s) you wish to be sent by Kermit",
      "You may enter a single \"wildcard\" name by",
      "pressing ADDTNL OPTIONS & selecting Extended filename",
      stat);

   printf(clrseq); fflush(stdout);	/* Erase the screen */

   /*
    * If MAIN SCREEN or EXIT pressed, exit from KERFIL.
    * Also exit if no files were selected.
    */
   if((nch == 0) || (stat[0] == 2 && (stat[1] == 9 || stat[1] == 10)))
      {
      xk_rst();				/* Reset comm port */
      ps_pmsg("User stopped KERMIT or selected no files");
      exits(EX$WAR);			/* WARNING - no files transferred */
      }

   /*
    * Complete by redirecting the file list and count
    */
   filelist = filvec;
   filecount = nch;
   gnxtfl();				/* This should succeed */
   }

/*
 * ps_xfil - Get a list of remote filespecs for server GET
 *
 * I find it hard to believe they didn't document the routine
 * they used to get an edited string in with echo, terminated 
 * by <RETURN> or a function key, passing back the function key
 * code in status ...
 */
static char fraseq[] = "\033[1;1H\033[J\033[7m\
                    Remote File Specification Selection Form\
                    \
\033[2;1H  \033[2;79H  \
\033[3;1H  \033[3;79H  \
\033[4;1H  \033[4;79H  \
\033[5;1H  \033[5;79H  \
\033[6;1H  \033[6;79H  \
\033[7;1H  \033[7;79H  \
\033[8;1H  \033[8;79H  \
\033[9;1H  \033[9;79H  \
\033[10;1H  \033[10;79H  \
\033[11;1H  \033[11;79H  \
\033[12;1H  \033[12;79H  \
\033[13;1H  \033[13;79H  \
\033[14;1H  \033[14;79H  \
\033[15;1H  \033[15;79H  \
\033[16;1H  \033[16;79H  \
\033[17;1H  \033[17;79H  \
\033[18;1H  \033[18;79H  \
\033[19;1H  \033[19;79H  \
\033[20;1H                                        \
                                        \033[0m\
\033[22;1HNOTE: File specs may contain wildcards as supported by remote system.\
\033[3;8HEnter up to 10 remote file specifications followed by RETURN\
\033[4;8Hthen press DO to start the transfer:";

static word frapar[] = {fraseq, sizeof(fraseq), 0, 0, 0, 0};

extern prscsi();

ps_xfil()
   {
   int stat[2];
   int i;
   word len;
   char posseq[20];			/* Cursor positioning sequence */

   for(i=0; i<MAXSELECT; i++)		/* Initialize file spec vector */
      filvec[i] = &filspecs[i][0];

   /*
    * Now make the phoney menu
    */
reptmnu:				/* Here to reset & do menu again */
   qiow(IO_WLB, 1, 1, 0, 0, frapar);	/* Draw the frame */

   /*
    * Input the file list.  Input stops when DO, EXIT or MAIN SCREEN
    * are pressed, or when RETURN is pressed on an empty line.  Any
    * other function key just clears out screen and restarts file
    * input.  Eventually, all but CANCEL should be a no-op but ...
    */
   for(i=0; i<MAXSELECT;)
      {
      sprintf(posseq, "\033[%d;8HFile %2d: ", i+6, i+1);
      if((len = get_line(filvec[i], 50, posseq, stat)) > 0)
         i++;
      if((len == 0) || 
         (stat[0] == 2 && (stat[1] == 9 || stat[1] == 10 || stat[1] == 16))
        )
         break;
      if(stat[0] == 2)			/* Other F-key */
         goto reptmnu;
      }         

   /*
    * If MAIN SCREEN or EXIT pressed, exit from KERFIL.
    * Also exit if no files were selected.
    */
   if((i == 0) || (stat[0] == 2 && (stat[1] == 9 || stat[1] == 10)))
      {
      xk_rst();				/* Reset comm port */
      ps_pmsg("User stopped KERMIT or selected no files");
      exits(EX$WAR);			/* WARNING - no files transferred */
      }

   /*
    * Complete by redirecting the file list and count
    */
   filelist = filvec;
   filecount = i;
   }


/*
 * Local routine for above function
 */
static char inpbuf[90];
static word inppar[] = {inpbuf, 80, 0, 0, 0, 0};

static get_line(buf, max, prompt, stat)
char *buf;
int max;
char *prompt;
int *stat;
   {
   word msgl;

   inppar[0] = buf;
   inppar[1] = max;
   inppar[3] = prompt;
   inppar[4] = strlen(prompt);
   qiow(IO_RPR, 1, 1, iosb, 0, inppar);
   if(iosb[0] == IS_CR)
      {
      buf[iosb[1]] = '\0';
      stat[0] = 1;
      stat[1] = 13;
      return(iosb[1]);
      }
   else
      {
      call(prscsi, 4, stat, buf, &iosb[1], &msgl);
      buf[msgl-1] = '\0';
      return(msgl - 1);
      }
   }


/*
 * ps_arm - Display "in progress" screen and arm ^C transfer aborts.
 */

static char proform[] = 
"\033[1;1H\033[J\033#3          Transfer in progress\
\033[2;1H\033#4          Transfer in progress\
\033[4;8HPress \033[1mINTERRUPT-DO\033[0m to abort the transfer ...\
\033[6;1H========================================\
========================================\
\033[22;1H========================================\
========================================\
\033[24;8HThe above information is being logged on your message board.\
\033[7;21r\033[7;1H";

static word propar[] = {proform, sizeof(proform), 0, 0, 0, 0};

ps_arm(abofl)				/* Enable user interrupts */
int *abofl;
   {
   qiow(IO_WLB, 1, 1, 0, 0, propar);	/* Put up the form */
   *abofl = FALSE;			/* Clear the abort flag */
   aboarm = TRUE;			/* Tell AST to use 1st ^C */
   return;
   }


/*
 * ps_can - Cancel transfer abort mechanism.
 *
 * Reset screen. ^C now will gracefully exit task
 */
 ps_can()
   {
   aboarm = aboflag = 0;	/* Watch it! aboarm is decremented */
   printf("%s\033[1;24r", clrseq); fflush(stdout);
   }


/*
 * astsrv - Service ^C (INTERRUPT-DO) inputs
 *
 * If "armed" for transfer abort, the 1st INTERRUPT-DO sets a flag
 * which causes a synchronous abort of the file transfer currently
 * underway.  If not, the first INTERRUPT-DO causes a fairly graceful
 * exit of the task.  If not armed, one INTERRUPT-DO is enough to
 * cause task exit.
 */
extern char *fp;			/* Open file pointer */
static astsrv()
   {
   astset();				/* Save environ. for AST */

   if(aboarm-- > 0)			/* If we were armed */
      aboflag = TRUE;			/* Set abort transfer flag */
   else					/* Real INTERRUPT-DO */
      {
      qiow(IO_KIL, 1, 1, 0, 0, 0, 0);	/* Kill terminal I/O */
      qiow(IO_KIL, COMM_LUN, COMM_EFN, 0, 0, 0, 0); /* Kill XK: I/O */
      fclose(fp);			/* Close input file */
      xk_rst();				/* Flush XK & restore I/O */
      p$msgbrd("User aborted KERMIT.", iosb);
      p$fatler("User abort, INTERRUPT-DO entered.");
      }
   astx(1);				/* Exit AST (remove 1 TDP) */
   }
