/************************************************************************
*  a terminal program that has ascii and xmodem transfer capability
*
*  originally written by Michael Mounier
*
* See file Aterm.mods for modification history.
*
************************************************************************/


/*  compiler directives to fetch the necessary header files */
#include <libraries/dos.h>
#include <devices/serial.h>
#include <fcntl.h>

#include <exec/types.h>
#include <stdio.h>
#include <intuition/intuition.h>
#include <intuition/intuitionbase.h>
#include <exec/memory.h>
#include <functions.h>
#include "Options.h"
#include "ConsoleIO.h"
#include "SerialIO.h"
#include "Timer.h"

#define INTUITION_REV 0L /* Use Current Version */
#define GRAPHICS_REV  0L /* Use Current Version */
#define VERSION  "Aterm 7.3"
#define CSI	 '\x9b'		/* command sequence introducer */
#define EOS      '\0' 		/* end of string marker        */
#define CTRLN    ('N'&0x1f)
#define	CHATON8	("\x9b3S\x9b22;1H\x9b1K\x9b21;1H\x9b21t")
#define CHATON9	("\x9b3S\x9b19;1H\x9b1K\x9b18;1H\x9b18t")
#define	CHOFF8	("\x9b24t")
#define CHOFF9	("\x9b21t")
#define WIPECH	("\x9b3T\x9b3S")

/* External variables declared here */

#define NUMKEYS	   10
#define KEYSIZE	   40
#define BUFSIZE    KEYSIZE * NUMKEYS
UBYTE   *KeyBuf  = NULL;	/* The Key Buffer */
BOOL	chatting = FALSE;
BOOL	DoPrefs  = TRUE;
BOOL    StartClock();
void    DeleteClock();

/* Intuition always wants to see these declarations */

struct IntuitionBase *IntuitionBase = NULL;
struct GfxBase *GfxBase = NULL;

/* Global variables local to this module */

static UBYTE name[ 32 ];
static BOOL  capture = FALSE;
static BOOL  sending = FALSE;
static BOOL  waiting = FALSE;
static FILE  *tranr, *trans;
static BOOL  ClockON = FALSE;
static ULONG waitmask;
static LONG  maxcount;

/* Defaults if no "Init.key" file. */
static struct options Options =
    {
      /* Baud      */  300,
      /* Mode      */  MODEFULL,
      /* AutoChop  */  TRUE,
      /* Xon       */  FALSE,
      /* Protocol  */  XMODEMCRC,
      /* Prompt    */  EOS	 /* None */
     } ;

static int baudrate[] = { 300,1200,2400,4800,9600 };

static struct Preferences *Preferences;
static struct IOExtSer *SerialIO = NULL;
static struct IOStdReq *Console = NULL;	/* see "ConsoleIO.h"	*/
static BOOL   TimerOpened = FALSE;

/* my window structure */
struct NewWindow NewWindow = {
   0,0,640,200, /* left, top, width, height */
   0,1, 	/* detail, block pens */
   CLOSEWINDOW | MENUPICK | GADGETUP,
   WINDOWCLOSE | SMART_REFRESH | ACTIVATE | WINDOWDEPTH | BORDERLESS,
   NULL,       /* firstgadget */
   NULL,       /* checkmark */
   (UBYTE *)VERSION, /* title */
   0,	       /* screen */
   NULL,       /* bitmap */
   0,0,        /* min x,y */
   640, 200,   /* max x,y */
   WBENCHSCREEN  /* type */
};

static struct Window *mywindow = NULL;     /* ptr to applications window */
static struct RastPort *rp;		   /* ptr to RastPort for Chat   */
static struct IntuiMessage *NewMessage;	   /* msg structure for GetMsg() */
static struct Menu *menuStrip = NULL;      /* from InitMenu()            */

/*--------------------------------------------------------------*/
/*	cleanup: all done so close up and prepare to exit	*/
/*--------------------------------------------------------------*/

static void cleanup()
{
   if ( ClockON )
	DeleteClock();
   if ( KeyBuf != NULL )
      FreeMem( KeyBuf, (long)BUFSIZE );
   if ( TimerOpened )
      CloseTimer();
   if ( SerialIO != NULL )
 	CloseSerialIO();
   if ( menuStrip != NULL )
	CloseMenu();    
   if ( mywindow != NULL )
	CloseWindow( mywindow );
   if ( Console != NULL )
	CloseConsoleIO();
   if ( GfxBase != NULL )
      CloseLibrary( GfxBase );
   if ( IntuitionBase != NULL )
      CloseLibrary(IntuitionBase);
}

/*--------------------------------------------------------------*/
/*	setdefaults: set defaults occording to current options. */
/*--------------------------------------------------------------*/

static void setdefaults()
{
   /* set defaults */

if (DoPrefs)
  {
   switch ( Preferences->BaudRate )
     {
	case 1:
	 Options.Baud = 300;
	 break;
	case 2:
	 Options.Baud = 1200;
	 break;
	case 3:
	 Options.Baud = 2400;
	 break;
	case 4:
	 Options.Baud = 4800;
	 break;
	case 5:
	 Options.Baud = 9600;
	 break;
	default:
	 Options.Baud = 300;
	 break;
     }
  }
   
   SetSerBaud( (int)Options.Baud);
   SetSerMode( (int)Options.Mode);
   SetXonMode( (BOOL)Options.Xon );
   SetMenu( &Options );
   DoPrefs = FALSE;
}

/*--------------------------------------------------------------*/
/*	init: initialize Aterm - set defaults			*/
/*--------------------------------------------------------------*/

static void init()
{
   extern struct Menu *InitMenu();
   static char initkeyname[] = "s:init.key";
   char *s;
   BOOL   initFound;
   int	  j;

    static char *credits[] =
   {"\x9b8y\xC\x9B33m\x9B5;17HA T E R M   V E R S I O N   7.3",
    "\x9B7;19HOriginal by Michael Mounier",
    "\x9B9;21HMajor Modifications by:",
    "\x9B10;26HJeff Lydiatt",
    "\x9B11;22HLarry Phillips (ICUG)",
    "\x9B12;27HSteve Allen",
    "\x9B14;24HSpecial Thanks to",
    "\x9B16;14H\x9B3mThe Independent Computer Users Group\x9B0;33m",
    "\x9B17;30Hand",
    "\x9B18;14H\x9B3mThe Source Telecomputing Corporation\x9B0;33m"
    "\x9B20;8HWho Made the Super Kermit Implementation Possible"
    "\x9Bm",
    NULL};

   IntuitionBase = (struct IntuitionBase *) 
                    OpenLibrary("intuition.library", INTUITION_REV);
   if( IntuitionBase == NULL )
      {
	puts("can't open intuition\n");
 	goto badopen;
      }

   Preferences = GetPrefs (Preferences, 4L);

   GfxBase = (struct GfxBase *) 
              OpenLibrary("graphics.library", GRAPHICS_REV);
   if( GfxBase == NULL )
      {
 	puts("can't open graphics library\n");
 	goto badopen;
      }
   
   if ( (TimerOpened = OpenTimer()) == FALSE )
      {
	puts( "Can't open the Timer." );
	goto badopen;
      }

   if(( mywindow = OpenWindow(&NewWindow) ) == NULL)
      {
	puts("Can't open a window.\n");
	goto badopen;
      }

   if(( Console = InitConsoleIO( mywindow ) ) == NULL)
      {
	puts("Can't open the console.\n");
	goto badopen;
      }

   if(( SerialIO = InitSerialIO() ) == NULL)
      {
	puts("Can't open the Serial Port.\n");
	goto badopen;
      }

   if (( KeyBuf= AllocMem( (long)BUFSIZE,
     (long)MEMF_PUBLIC | MEMF_CLEAR))== NULL)
      {
	puts("Can't get memory for function keys.\n");
	goto badopen;
      }
 
   menuStrip = InitMenu( mywindow );

   j = 0;
   while ( (s = credits[j++]) != NULL )
      PutString( s );

   CursorXY(1, 1);
   if ( (ClockON = StartClock( mywindow )) == TRUE )
      PutString( "\x9b33mClock Task Started.\n\x9bm" );

   initFound = LoadKeys( initkeyname );	    /* did the default f-keys load? */
   if ( LoadKeys( &initkeyname[2] ) )
      initFound = TRUE;
   if ( initFound )
	PutString( "\x9b33mFunction Keys Loaded...\n\x9bm" );
   else
      setdefaults();

   rp = mywindow->RPort;

   CursorXY(1, 22);
   return;

badopen:
   cleanup();
   exit( 4 );
}

/*--------------------------------------------------------------*/
/*	SetOptions: called by LoadKeys to replace all options.  */
/*--------------------------------------------------------------*/

void SetOptions( opt, optsize )
register char *opt;
int optsize;
{
   register int i;
   register char *s;

   /* Note future changes can be easily accomplished by testing */
   /* for the optsize of the older versions, and making the     */
   /* appropriate defaults and/or sending an error message.	*/
   
   if (optsize != sizeof( Options ) )
     {
       optsize = sizeof( Options );
       PutString( "Can't happen: setoptions got a bad option size." );
       if ( optsize > sizeof( Options ) )
	  optsize = sizeof( Options );
     }

   /* copy the options */
   /* The trick here is to pretend Options is an array of chars */
   /* for the purpose of copying the options passed by LoadKeys */

   s = (char *)&Options.Baud;
   for (i = 0; i < optsize; i++)
      *s++ = *opt++;

   setdefaults();
}

/*--------------------------------------------------------------*/
/*	GetOptions: called by SaveKeys to store current options.*/
/*--------------------------------------------------------------*/

int GetOptions( opt, maxsize )
register char *opt;
int maxsize;
{
   register int i;
   register char *s;

   if (maxsize > sizeof( Options ) )
      maxsize = sizeof( Options );

   /* copy the options */
   /* Note the same trick of treating Options as an array of chars*/
   /* is used here to copy the current options to a workspace     */
   /* provided by SaveKeys.                                       */

   s = (char *)&Options.Baud;
   for (i = 0; i < maxsize; i++)
      *opt++ = *s++;

   return sizeof( Options );
}
static void GetFunc(s)
char *s;
{
    char name[40];

    strcpy(name, "s:");
    strcat(name,s);
 
    if (LoadKeys(&name[2]))
       PutString("\x9b33mLoaded\n\x9bm");
    else if (LoadKeys(&name[0]))
       PutString("\x9b33mLoaded\n\x9bm");
    else
       PutString("\x9b33mCouldn't load that file\n\x9bm");
}

/*--------------------------------------------------------------*/
/*          doFileMenu: Handle Menu 0 			        */
/*--------------------------------------------------------------*/
static void doFileMenu( itemnum, subnum )
int itemnum, subnum;
{
   UBYTE c;
   BOOL  status;

   switch( itemnum )
     {
       /*-----------------------------------*/
       /* 	Ascii Receive		    */
       /*-----------------------------------*/	
       case 0:
	if (capture)
	  {
	     capture = FALSE;
	     fclose(tranr);
	     PutString("\x9b33m\nEnd File Capture\n\x9bm");
          }
	else
    	  {
	     if ( !GetLine("\x9b33m\nAscii Capture: ", name))
	        PutString("\nAborted\n\x9bm");
	     else
               {
 		  if ((tranr=fopen(name,"w")) == 0)
 		    {
			capture=FALSE;
			PutString("\nCan't Open File\n\x9bm");
			break;
 		    }
		  capture = TRUE;
		  PutString("\x9bm");
	       }
 	  }
	break;

       /*-----------------------------------*/
       /* 	Ascii Send		    */
       /*-----------------------------------*/	
        case 1:
	if (sending)
	  { 
	     sending = waiting = FALSE;
	     fclose(trans);
	     PutString("\n\x9b33mFile Send Cancelled\n\x9bm");
	   }
 	else if ( !GetLine("\x9b33m\nAscii Send: ",name) )
	    PutString("\nAborted\n\x9bm");
	else if ( (trans = fopen(name,"r")) == 0)
	   {
	      sending = FALSE;
	      PutString("\nCan't Open File\n\x9bm");
	    }
	else
	    {
	      sending = TRUE;
	      waiting = FALSE;
 	      PutString("\x9bm");
	    }
	break;

       /*-----------------------------------*/
       /* 	Binary Receive		    */
       /*-----------------------------------*/	
       case 2:
	if ( !GetLine("\x9b33m\nBinary Receive: ", name))
	  PutString("\nAborted\n\x9Bm");
	else
	  {
	    if ( Options.Protocol == KERMIT )
		status = KermitGet( name );
	    else
	      {
		/* disable ^Q/^S trapping */
		PushSerState();
		SetXonMode( FALSE );
		status = XMODEM_Read_File(name,
                        (BOOL)(Options.Protocol == 1), (BOOL)Options.AutoChop);
		PullSerState();
	      }
	    if ( status )
		PutString("\nFile Received\n\x9bm");
	    else
		PutString("\nBinary Receive Failed\n\x9bm");
	    DisplayBeep(NULL);
	  }
	break;
 
       /*-----------------------------------*/
       /* 	Binary Send		    */
       /*-----------------------------------*/	
       case 3:
	if ( !GetLine("\x9b33m\nBinary Send: ",name))
	   PutString("\nAborted\n\x9bm");
	else
	  {
	    if ( Options.Protocol == KERMIT )
	       status = KermitPut( name );
	    else
	       status = XMODEM_Send_File( name, (int)Options.Baud );
	    if ( status )
		PutString("\nFile Sent\n\x9bm");
	     else
		PutString("\nBinary Send Failed\n\x9bm");
	    DisplayBeep(NULL);

	  }
	break;

       /*-----------------------------------*/
       /* 	Prompt			    */
       /*-----------------------------------*/	
       case 4:
	PutString("\x9b33m\nNew Prompt Character: ");
	if ( (c = GetKey()) == '0')
 	   Options.Prompt = EOS;
	else 
	   Options.Prompt = c;
	keychar( c );
	PutString("\n\x9bm");
        break;
    }
}

/*----------------------------------------------------------*/
/*  doMiscMenu: Handle the miscellaneous menus		    */
/*----------------------------------------------------------*/
static void doMiscMenu( itemnum, subnum )
int itemnum, subnum;
{
   switch (itemnum)
     {
       /*---------------------------------*/
       /*       FKey Load		  */
       /*---------------------------------*/
       case 0: /* FKey load */
	if ( !GetLine("\x9b33m\nFunction-Key file name: ",name))
	   PutString(" aborted.\n\x9bm");
	else
	  { 
	    GetFunc(name);
	  }
	break;

       /*---------------------------------*/
       /*       FKey Save		  */
       /*---------------------------------*/
       case 1:
	if ( !GetLine("\x9b33m\nFunction-Key Save Filename: ",name))
	   PutString( "aborted...\n\x9bm" );
	else
	  {
	    if (SaveFKeys(name))
	       PutString( "File saved\n\x9bm" );
	    else
	       PutString( "Unable to save that file.\n\x9bm" );
	  }
	break;

       /*---------------------------------*/
       /*       define an FKey		  */
       /*---------------------------------*/
       case 2:
	DefineString();
	break;

       /*---------------------------------*/
       /*       Toggle AutoChop		  */
       /*---------------------------------*/
       case 3:
	Options.AutoChop = (subnum == 0);
	break;

       /*---------------------------------*/
       /*       Toggle Xon Mode		  */
       /*---------------------------------*/
       case 4:
	Options.Xon = (subnum == 0);
	SetXonMode( (BOOL)Options.Xon );
	break;

       /*---------------------------------*/
       /*	Set Protocol		  */
       /*---------------------------------*/
       case 5:
	Options.Protocol = subnum;
	break;

       /*---------------------------------*/
       /*      CHAT Mode toggle           */
       /*---------------------------------*/
       case 6:
	chatting = !chatting;
	Chat( chatting );
	break;

       /*---------------------------------*/
       /*	Do Phone Book		  */
       /*---------------------------------*/
       case 7:
	 do_phlib( mywindow );
	break;

    }
}
/*============================================================*/
/*== CHAT functions. Moved here because I couldn't get them ==*/
/*== working in a separate file.			    ==*/
/*============================================================*/

/*---------------------------------*/
/* Chat - Turns CHAT window on/off */
/*---------------------------------*/

Chat( flag )
BOOL flag;

{
char cursor = '_';

if (flag)
  {
	if (Preferences->FontHeight == 8)
	 {
	  PutString ( CHATON8 );
	  Move ( rp, 0L, 198L );
	  RectFill( rp, 0L, 178L, 640L, 181L);
	  maxcount = 79;
	 }
	if (Preferences->FontHeight == 9)
	 {
	  PutString ( CHATON9 );
	  Move ( rp, 0L, 196L );
	  RectFill( rp, 0L, 173L, 640L, 176L);
	  maxcount = 63;
	 }
    Text ( rp, &cursor, 1L );
  }
else
  {
    if (Preferences->FontHeight == 8)
	PutString ( CHOFF8 );
    if (Preferences->FontHeight == 9)
     {
	ScrollRaster( rp,0L, -20L,0L,180L,640L,200L );
	PutString ( CHOFF9 );
     }
    PutString( WIPECH );
    ChatOut  ( 0 );
  }
}

/*-----------------------------------------*/
/* ChatOut - Send character to CHAT window */
/*	   - Send to SerIO if CR/LF        */
/*-----------------------------------------*/

ChatOut( c )
UBYTE c;

{
static	LONG	count = 0;
static	UBYTE	line[ 81 ] = '_';

int	i;

Move ( rp, 0L, 197L );

switch (c)
     {
	case '\r':
	case '\n':
	  line[count] = '\r';
	  for ( i = 0; i < count; i++ )
	    SerIOPut(line[i]);
	  SerIOPut ('\r');
	  line[0] = '_';
	  count = 0;
	  if (Preferences->FontHeight == 8)
	    ScrollRaster( rp,0L,(LONG)Preferences->FontHeight,0L,183L,640L,200L );
	  if (Preferences->FontHeight == 9)
	    ScrollRaster( rp,0L,(LONG)Preferences->FontHeight,0L,181L,640L,200L );
	  Text( rp, line, 1L );
	 break;

	case '\x08':
	  if (count > 0)
	   {
	    line [count--] = ' ' ;
	    line [count]   = '_';
	    Text( rp, line, count+2 );
	   }
	  break;

	case '\0':
	  count   = 0;
	  line[0] = '_';
	break;

	default:
	  if ( count < maxcount )
	   {
	    line[count++] = c;
	    line[count]   = '_';
	    Text( rp, line, count+1 );
	   }
	  else
	   PutChar('\x07');
	break;
     }
}

/******************************************************/
/*		     Main Program		      */
/*						      */
/*	This is the main body of the program.	      */
/******************************************************/

main()
{
ULONG class, waitmask;
int code, menunum, itemnum, subnum;
BOOL KeepGoing;
int c, i;
UBYTE character, *KeyOffset;

   init();

   waitmask = (1L << SerialIO->IOSer.io_Message.mn_ReplyPort->mp_SigBit)
           |  (1L << mywindow->UserPort->mp_SigBit)
           |  (1L << Console->io_Message.mn_ReplyPort->mp_SigBit);

   KeepGoing = TRUE;
   while( KeepGoing )
     {

      if ( !sending || waiting )
          Wait( waitmask );

       /* --- Ascii Send Routine interleaved ----------*/
       if ( sending && !waiting /* for the Prompt character */ )
         {
	   if ( (c = getc(trans)) == EOF)
	     {
		fclose( trans );
		PutString( "\x9b33m\nFile Sent\n\x9bm" );
		sending = waiting = FALSE;
	     }
	   else if (c == '\n' )
	     {
		SerIOPut('\r');  /* switch in carriage return	*/
		if ( Options.Prompt != EOS )
		    waiting=TRUE;
	     }
	   else SerIOPut(c);
	 } /* end (if sending) */

      /*-----  Drain the keyboard buffer--------------*/

      while ( c = CheckKey() )
	{
	   if (c != CSI)
	    {
	      if (chatting)
		ChatOut( c );
	      else
		SerIOPut( c );
	    }
	   else
	     {
		character = GetKey();
		if ((character < 'A') || (character > 'D'))
		   while ( GetKey() != '~');

		if ( ('0' <= character) && (character <= '9') )
		  {
		    KeyOffset = KeyBuf + (KEYSIZE * (character - '0'));
		    while ( c = *KeyOffset++ )
		      {
			if (CheckKey() == 27)
			  {
			    PutString("\x9b33m\nFunction key aborted.\x9bm\n");
			    break;
			  }
			if ( c > 127 )
			  {
			    switch (c)
			      {
			        case 128:		/* <LOAD> */
				  GetFunc(KeyOffset);
				  while (c = *KeyOffset++);
				   KeyOffset--;
				  break;
				case 129:		/* <LOADEX */
				  c = *KeyOffset++;
				  GetFunc(KeyOffset);
				  KeyOffset=KeyBuf + (KEYSIZE * (c - '0' -1));
				  break;
				default:
				  if (( c > 129 ) && ( c < 140 )) /* <Fn:> */
				    KeyOffset=KeyBuf+(KEYSIZE*(c - 83 - '0'));
				  break;
			      }			/* end case (c)   */
			  }			/* end if (c>127) */
			else
			 SerIOPut( c );
		      }				/* end while */
		  }				/* end if    */
		else if (character == '?')
		  {
		     PutString( "\x9b33m\n" ); /* in red */
		     PutString( VERSION );
 		     PutString( "\n\nPrompt character is  " );
		     if ( Options.Prompt == EOS)
			PutString( "Inactive" );
		     else
			keychar( Options.Prompt );
		     PutString("\n\n");
		     DoContents();		/* display function keys */
		     PutString("\x9bm");	/* turn off red */
		  }

	     }    /* end if CSI */
	}   /* end while CheckKey() */

     /*----- Drain the Serial Port Buffer-------------*/

     if ( CheckSerIO() )
        {
	  do
	    {
	      c = SerIOGet() & 0x7f;
	      if ((c != '\n') && (c != CTRLN))	/* don't print LF or CTRL N */
	      PutFormat(c=='\r'?'\n':c);	/* convert CR into LF */

	      if ( (waiting) && (c == Options.Prompt) )
	         waiting = FALSE;
	      if (capture)
	         if ( (' ' <= c && c < '\x7f') || c == '\n' || c== '\t' )
		    putc( c, tranr ); /* trash them mangy ctl chars */
	    } while( CheckSerIO() );
	 PutFormat((UBYTE)'\xff');	/* squirt out all received chars */
       }
     
     /* ---------------- Handle all Menu Messages --------------------*/

     while( NewMessage=(struct IntuiMessage *)GetMsg(mywindow->UserPort) )
	{
	  class = NewMessage->Class;
	  code = NewMessage->Code;
	  ReplyMsg( NewMessage );
	  switch( class )
	    {
	      case CLOSEWINDOW:
		   KeepGoing = FALSE;
	  	   break;

	      case MENUPICK:
		   if (	code != MENUNULL )
		     {
		       menunum = MENUNUM( code );
		       itemnum = ITEMNUM( code );
		       subnum  =  SUBNUM( code ); 
		       switch( menunum )
			 {
			   case 0:
				doFileMenu( itemnum, subnum );
				break;
			   case 1:		/* New Baud Rate */
				Options.Baud = baudrate[ itemnum ];
				SetSerBaud( (int)Options.Baud );
				break;
			    case 2:		   /*  "Mode" */
				Options.Mode = itemnum;
				SetSerMode( (int)Options.Mode );
				break;

			    case 3:	/* misc. menu selection */
				doMiscMenu( itemnum, subnum );
				break;
		        } /* end of switch ( menunum ) */
	           }    /*end of for( not MENUNULL ) */
	    }   /* end of switch (class) */
     }   /* end of while ( newmessage )*/
  }	/* end while ( keepgoing ) */

   cleanup();
   exit( 0 );
} /* end of main */
