/*
    TERMINAL.C
    ----------
    
    Funktion:	Emulation eines Terminals mit ELZET CPU/IEC und VIDEO 
		80 Karte. Die RS232 Druckerschnittstelle auf der 
		CPU/IEC Karte fungiert dabei als Terminalport. 
		Achtung, nach dem Aufruf von Terminal muss ein 
		Kaltstart durchgefuehrt werden, bevor die 
		Druckerschnittstelle erneut verwendet werden kann.
    
    Aufruf:	Als CP/M Programm durch "A>TERMINAL".
    
    Optionen:	Die folgenden Optionen stehen zur Verfuegung und 
		werden in der obersten Zeile (Terminal Status) 
		angezeigt:
							Default:
		PROT	--	XON , --- , DTR		---
		BAUD    --	9600 , 19200 , 38400	9600
		BIT	--	5 , 7 , 8		8
		SBIT	--	1 , 1.5 , 2		2
		PARITY	--	np , pe , po		pe
		INFIL	--	<<Eingabedatei>>
		OUTFIL	--	<<Ausgabedatei>>
		
		Alle Optionen haben nach dem Programmstart die Defaultwerte als
		Grundeinstellung. Sie koennen waehrend des Terminalbetriebs 
		nach Umschalten mit  ESC  geaendert werden.
*/


#include "stdio.h"

/* physikalische Terminalgroesse */
#define MAXLIN     23
#define MAXCOL     80

/* Steuerzeichen (Eingang) */
#define CTRLC	 0x03
#define BS       0x08
#define TAB      0x09
#define LF       0x0A
#define CR       0x0D
#define FF       0x0C
#define AXON     0x11
#define AXOFF    0x13

/* Steuerzeichen Tastatur/Video */
#define LEFT     0x08
#define OK       0x0D
#define SWITCH   0x1B
#define _BS      0x08
#define _TAB     0x09
#define _LF      0x0A
#define _CR      0x0D
#define _FF      0x1A

/* allgemeine Steuerzeichen */
#define NOTRDY     -1
#define QUIT        0
#define PRV        -1
#define NXT         1
#define MAXITEM     7

/* Betriebsarten */
#define NOP         0
#define DTR         1
#define XON         2

/* SIO Adressen */
#define DAT      0x04
#define CON      0x06

/* Status Reset SIO */
#define RESEXT   0x10


/*
    statische Variable
    ------------------
*/

static int lin;
static int col;
static int xmit;
static int reinit;
static int mode;
static int mask;

static FILE *input = NULL;
static FILE *output = NULL;

static char empty[] = "--------------\0";
static char ruler[] =
"  09600 bd  8/2 Bit  pe   --- Protokoll  << --------------  >> --------------   ";

static char sio_reg[] =
  {
    0x18,	/* Channel reset */
    0x00,	/* No interrupts */
    0x00,	/* No interrupt vector */
    0xC1,	/* 8 bit/Rx Char, Rx enable */
    0xCF,	/* 16x clock (=9600 bd), 2 Stopbits, even parity */
    0xE8,	/* DTR, 8 bit/Tx char, Tx enable, no RTS */
    0x00,	/* no sync */
    0x00
  };


/*	sd_rs232()	Send character via RS232	*/

sd_rs232(ch)
char ch;
{
  ch = ch & mask;
  if (!(inp(CON) & 0x04))	/* Tx buffer empty? */
    return (NOTRDY);
  outp(DAT,ch);
  return(0);
}


/*	s_rs232 ()	Send character according to protocol	*/

s_rs232 (ch)
char ch;
{
  if (reinit)
    {
      i_rs232(sio_reg,sizeof(sio_reg)-1,1);
      reinit = FALSE;
    }
  switch (mode)
    {
      case NOP:
      return (sd_rs232(ch));
      
      case DTR:
      outp(CON,RESEXT);
      if (!(inp(CON) & 0x40))
	return (NOTRDY);
      return (sd_rs232(ch));
      
      case XON:
      if (xmit)
	return (sd_rs232(ch));
      else
	return (NOTRDY);
    }
}


/*	rd_rs232 ()	Read character from RS232	*/

rd_rs232 ()
{
  if (!(inp(CON) & 0x01))
    return (NOTRDY);
  return (inp(DAT) & mask);
}


/*	r_rs232 ()	Read character according to protocol	*/

r_rs232 ()
{
  register int c;
  if (reinit)
    {
      i_rs232(sio_reg,sizeof(sio_reg)-1,1);
      reinit = FALSE;
    }
  switch (mode)
    {
      case NOP:
      return (rd_rs232());
      
      case DTR:
      if ((c = rd_rs232()) != NOTRDY)
	{
	  outp(CON,5);
	  outp(CON,sio_reg[5] & 0x7F);
	  return (c);
	}
      else
	{
	  outp(CON,5);
	  outp(CON,sio_reg[5] | 0x80);
	  return (NOTRDY);
	}
      
      case XON:
      if ((c = rd_rs232()) == NOTRDY)
	{
	  while (sd_rs232(AXON) == NOTRDY);
	  return (c);
	}
      else
	{
	  if (c == AXON)
	    {
	      xmit = TRUE;
	      return (r_rs232());
	    }
	  else if (c == AXOFF)
	    {
	      xmit = FALSE;
	      return (r_rs232());
	    }
	  else
	    {
	      while (sd_rs232(AXOFF) == NOTRDY);
	      return (c);
	    }
	}
    }
}


/*	i_rs232 ()	Init. SIO	*/

i_rs232 (p,l,s)
char p[];
{
  register int i;
  for (i=s; i<l; i++)
    {
      outp(CON,i);
      outp(CON,p[i]);
    }
}


/*	gc ()	Get character from console	*/

gc ()
{
  register int c;
  if ((c = cpm(6,0xFF)) == 0)
    c = NOTRDY;
  return (c);
}


/*	pc ()	Direct console output	*/

pc (ch)
char ch;
{
  cpm(6,ch);
}


/*	ps ()	Put string	*/

ps (s)
char *s;
{
  while (*s)
    pc(*s++);
}


/*	ivon ()		Inverse on	*/

ivon ()
{
  pc(0x1B);
  pc('J');
}


/*	ivoff ()	Inverse off	*/

ivoff ()
{
  pc(0x1B);
  pc('K');
}


/*	yx ()		Cursor position	*/

yx (y,x)
{
  pc(0x1B);
  pc('=');
  pc(y + ' ');
  pc(x + ' ');
}


/*	r_kbd ()	Read from keyboard or input file	*/

r_kbd ()
{
  register int c;
  if ((c = gc()) == SWITCH)		/* ESC switch */
    {
      newpar();
      return (r_kbd());
    }
  if (input != NULL)
    {
      c = getc(input);
      if (c == CPMEOF || c == EOF)
	{
	  fclose(input);
	  input = NULL;
	  ivon ();
	  yx (0,44);
	  ps (empty);
	  ivoff ();
	  yx (lin+1,col);
	  c = r_kbd();
	}
    }
  return (c);
}


/*	s_dsp ()	Send to display or output file	*/

s_dsp (ch)
char ch;
{
  if (output != NULL)
    {
      if (putc(ch,output) == EOF || ch == CPMEOF)
	{
	  fclose(output);
	  output = NULL;
	  ivon ();
	  yx (0,63);
	  ps (empty);
	  ivoff ();
	  yx (lin+1,col);
	}
    }
  pc (ch);
}


/*	sndstat ()	Display status line	*/

sndstat ()
{
  yx (0,0);
  ivon ();
  ps (ruler);
  ivoff ();
  yx (lin+1,col);
}


/*	copy ()		copy buffer into status line	*/

copy (buf,pos,len)
char *buf;
{
  while (len--)
    {
      if (*buf)
	ruler[pos++] = *buf++;
      else
	ruler[pos++] = ' ';
    }
}


/*	getch ()	Get character into buffer	*/

getch (buf,pos,cnt)
char *buf;
{
  static int ch,n;
  yx (0,pos);
  n = cnt;
  while (n--)
    {
      while ((ch = gc ()) == NOTRDY);
      ch = tolower(ch);
      if (ch == OK)
	break;
      else if (ch == SWITCH)
	{
	  sndstat ();
	  return (QUIT);
	}
      else if (ch == LEFT)
	{
	  if (cnt == n+1)
	    return (PRV);
	  else
	    {
	      pc (_BS);
	      pc (ruler[pos+cnt-n-2]);
	      pc (_BS);
	      buf--;
	      n += 2;
	    }
	}
      else
	{
	  pc (ch);
	  *buf++ = ch;
	}
    }
  *buf = 0;
  return (NXT);
}


/*	prnchr ()	Print characters on display	*/

prnchr (lin,pos,cnt)
char *lin;
{
  yx (0,pos);
  while (cnt--)
    pc (lin[pos++]);
}


/*	item ()		Get communication parameter	*/

item (no)
{
  static char buffer[20];
  static int res;
  switch (no)
    {
      case 0:					/* Baudrate */
      if ((res = getch(buffer,2,5)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (strcmp("09600",buffer) == 0)
	{
	  copy (buffer,2,5);
	  sio_reg[4] = sio_reg[4] & 0x3F;
	  sio_reg[4] = sio_reg[4] | 0xC0;
	  reinit = TRUE;
	}
      else if (strcmp("19200",buffer) == 0)
	{
	  copy (buffer,2,5);
	  sio_reg[4] = sio_reg[4] & 0x3F;
	  sio_reg[4] = sio_reg[4] | 0x80;
	  reinit = TRUE;
	}
      else if (strcmp("38400",buffer) == 0)
	{
	  copy (buffer,2,5);
	  sio_reg[4] = sio_reg[4] & 0x3F;
	  sio_reg[4] = sio_reg[4] | 0x40;
	  reinit = TRUE;
	}
      else
	{
	  prnchr(ruler,2,5);
	}
      break;
      
      case 1:					/* Bit pro Zeichen */
      if ((res = getch(buffer,12,1)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (strcmp("5",buffer) == 0)
	{
	  copy (buffer,12,1);
	  sio_reg[3] = sio_reg[3] & 0x3F;
	  sio_reg[3] = sio_reg[3] | 0x00;
	  sio_reg[5] = sio_reg[5] & 0x9F;
	  sio_reg[5] = sio_reg[5] | 0x00;
	  reinit = TRUE;
	  mask = 0x1F;
	}
      else if (strcmp("6",buffer) == 0)
	{
	  copy (buffer,12,1);
	  sio_reg[3] = sio_reg[3] & 0x3F;
	  sio_reg[3] = sio_reg[3] | 0x80;
	  sio_reg[5] = sio_reg[5] & 0x9F;
	  sio_reg[5] = sio_reg[5] | 0x40;
	  reinit = TRUE;
	  mask = 0x3F;
	}
      else if (strcmp("7",buffer) == 0)
	{
	  copy (buffer,12,1);
	  sio_reg[3] = sio_reg[3] & 0x3F;
	  sio_reg[3] = sio_reg[3] | 0x40;
	  sio_reg[5] = sio_reg[5] & 0x9F;
	  sio_reg[5] = sio_reg[5] | 0x20;
	  reinit = TRUE;
	  mask = 0x7F;
	}
      else if (strcmp("8",buffer) == 0)
	{
	  copy (buffer,12,1);
	  sio_reg[3] = sio_reg[3] & 0x3F;
	  sio_reg[3] = sio_reg[3] | 0xC0;
	  sio_reg[5] = sio_reg[5] & 0x9F;
	  sio_reg[5] = sio_reg[5] | 0x60;
	  reinit = TRUE;
	  mask = 0xFF;
	}
      else
	{
	  prnchr(ruler,12,1);
	}
      break;
      
      case 2:					/* Stopbits */
      if ((res = getch(buffer,14,1)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (strcmp("1",buffer) == 0)		/* 1 = 1 Stopbit */
	{
	  copy (buffer,14,1);
	  sio_reg[4] = sio_reg[4] & 0xF3;
	  sio_reg[4] = sio_reg[4] | 0x04;
	  reinit = TRUE;
	}
      else if (strcmp("2",buffer) == 0)	/* 2 = 2 Stopbits */
	{
	  copy (buffer,14,1);
	  sio_reg[4] = sio_reg[4] & 0xF3;
	  sio_reg[4] = sio_reg[4] | 0x0C;
	  reinit = TRUE;
	}
      else if (strcmp("3",buffer) == 0)	/* 3 = 1,5 Stopbits */
	{
	  copy (buffer,14,1);
	  sio_reg[4] = sio_reg[4] & 0xF3;
	  sio_reg[4] = sio_reg[4] | 0x08;
	  reinit = TRUE;
	}
      else
	{
	  prnchr(ruler,14,1);
	}
      break;
      
      case 3:					/* Paritaet */
      if ((res = getch(buffer,21,2)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (strcmp("np",buffer) == 0)
	{
	  copy (buffer,21,2);
	  sio_reg[4] = sio_reg[4] & 0xFE;
	  reinit = TRUE;
	}
      else if (strcmp("pe",buffer) == 0)
	{
	  copy (buffer,21,2);
	}
      else if (strcmp("po",buffer) == 0)
	{
	  copy (buffer,21,2);
	}
      else
	{
	  prnchr(ruler,21,2);
	}
      break;
      
      case 4:					/* Protokoll */
      if ((res = getch(buffer,26,3)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (strcmp("---",buffer) == 0)
	{
	  copy (buffer,26,3);
	  mode = NOP;
	}
      else if (strcmp("xon",buffer) == 0)
	{
	  copy (buffer,26,3);
	  mode = XON;
	}
      else if (strcmp("dtr",buffer) == 0)
	{
	  copy (buffer,26,3);
	  mode = DTR;
	}
      else
	{
	  prnchr(ruler,26,3);
	}
      break;
      
      case 5:					/* Input File */
      if ((res = getch(buffer,44,14)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (buffer[0] < 'A')
	{
	  if (input != NULL)
	    {
	      fclose(input);
	      input = NULL;
	      copy (empty,44,14);
	      prnchr(ruler,44,14);
	      break;
	    }
	  else
	    {
	      copy (empty,44,14);
	      prnchr(ruler,44,14);
	      break;
	    }
	}
      if ((input = fopen(buffer,"r")) == NULL)
	prnchr(ruler,44,14);
      else
	copy (buffer,44,14);
      break;
      
      
      case 6:					/* Output File */
      if ((res = getch(buffer,63,14)) == QUIT)
	return (QUIT);
      else if (res == PRV)
	return (PRV);
      if (buffer[0] < 'A')
	{
	  if (output != NULL)
	    {
	      fclose(output);
	      output = NULL;
	      copy (empty,63,14);
	      prnchr (ruler,63,14);
	      break;
	    }
	  else
	    {
	      copy (empty,63,14);
	      prnchr (ruler,63,14);
	      break;
	    }
	}
      if ((output = fopen(buffer,"w")) == NULL)
	prnchr (ruler,63,14);
      else
	copy (buffer,63,14);
      break;
    }
  return (NXT);
}


/*	newpar ()	Set new communication parameters	*/

newpar ()
{
  static int no;
  static int res;
  ivon ();
  for (no=0; no < MAXITEM; no++)
    if ((res = item (no)) == QUIT)
      break;
    else if (res == PRV)
      {
	if (no)
	  no -= 2;
	else
	  break;
      }
  ivoff ();
  yx (lin+1,col);
}


/*	display ()	Display input character on screen	*/

display (ch)
char (ch);
{
  register int n;
  switch (ch)
    {
      case CR:
      col = 0;
      s_dsp (_CR);
      break;

      case TAB:
      n = 8 - (col % 8);
      while (n--)
	display (' ');
      break;

      case FF:
      col = lin = 0;
      s_dsp (_FF);
      sndstat ();
      
      case LF:
      if (lin != MAXLIN-1)
	{
	  lin++;
	  s_dsp (_LF);
	}
      else
	{
	  s_dsp (_LF);
	  sndstat ();
	}
      break;
      
      case BS:
      if (col)
	{
	  col--;
	  s_dsp (_BS);
	}
      else if (lin)
	{
	  lin--;
	  col = MAXCOL -1;
	  s_dsp (_BS);
	}
      break;

      case CTRLC:
      exit(0);
	      
      default:
      if (ch >= 0x20 && ch <= 0x7F)
	{
	  s_dsp (ch);
	  col++;
	    if (col == MAXCOL)
	      {
		col = 0;
		if (lin != MAXLIN-1)
		  lin++;
		else
		  sndstat ();
	      }
	}
      break;
    }
}


/*	main ()		Main program (at last)	*/

main ()
{
  static int ich,och;
  static int notsnd;
  i_rs232 (sio_reg,sizeof(sio_reg),0);
  mask = 0xFF;
  mode = col = lin = 0;
  s_dsp (_FF);
  sndstat ();
  xmit = TRUE;
  reinit = notsnd = FALSE;
  while (1)
    {
      if ((ich = r_rs232 ()) != NOTRDY)
	display (ich);
      if (notsnd)
	{
	  if (s_rs232 (och) != NOTRDY)
	    notsnd = FALSE;
	}
      else
	{
	  if ((och = r_kbd ()) != NOTRDY)
	    if (s_rs232 (och) == NOTRDY)
	      notsnd = TRUE;
	}
    }
}
