/************************************************************************
*  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 31L /* V1.1 */
#define GRAPHICS_REV  31L /* V1.1 */
#define VERSION  "Aterm 7.0"
#define CSI	 '\x9b'		/* command sequence introducer */
#define EOS      '\0' 		/* end of string marker        */
#define CTRLN    ('N'&0x1f)

/* External variables declared here */

#define NUMKEYS   10
#define KEYSIZE   40
#define BUFSIZE   KEYSIZE * NUMKEYS
UBYTE   *KeyBuf = NULL;	/* The Key Buffer */
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;

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

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

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

/* my window structure */
static struct NewWindow NewWindow = {
   0,0,640,200, /* left, top, width, height */
   0,1, 	/* detail, block pens */
   CLOSEWINDOW | MENUPICK,
   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 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 */
   SetSerBaud( (int)Options.Baud);
   SetSerMode( (int)Options.Mode);
   SetXonMode( (BOOL)Options.Xon );
   SetMenu( &Options );
}

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

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

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

   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 );

   PutChar(12);	/* clears the screen */

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

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

   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 );
}

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

   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");
	else
	  {
	    /* disable ^Q/^S trapping */
  	    PushSerState();
	    SetXonMode( FALSE );
	    XMODEM_Read_File(name,
                        (BOOL)(Options.Protocol == 1), (BOOL)Options.AutoChop);
	    PullSerState();
	    DisplayBeep(NULL);
	  }
	PutString("\x9bm");
	break;
 
       /*-----------------------------------*/
       /* 	Binary Send		    */
       /*-----------------------------------*/	
       case 3:
	if ( !GetLine("\x9b33m\nBinary Send: ",name))
	   PutString("\nAborted\n\x9bm");
	else
	  {
	    if (XMODEM_Send_File( name, (int)Options.Baud ))
	      {
		PutString("\nFile Sent\n\x9bm");
		DisplayBeep(NULL);
	      }
	     else
	      {
		PutString("\nXmodem Send Failed\n\x9bm");
		DisplayBeep(NULL);
	      }
	  }
	break;

       /*-----------------------------------*/
       /* 	Prompt			    */
       /*-----------------------------------*/	
       case 4:
	PutString("\x9b33mNew 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 */
	name[0]='s'; name[1]=':';
	if ( !GetLine("\x9b33mFunction-Key file name: ",&name[2]))
	   PutString(" aborted.\n\x9bm");
	else
	  { 
	    if (LoadKeys(&name[2]))
	       PutString("Loaded\n\x9bm");
	    else if (LoadKeys(name))
	       PutString("Loaded\n\x9bm");
	    else
	       PutString("Couldn't load that file\n\x9bm");
	  }
	break;

       /*---------------------------------*/
       /*       FKey Save		  */
       /*---------------------------------*/
       case 1:
	if ( !GetLine("\x9b33mFunction-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;
    }
}

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

main()
{
ULONG class, waitmask;
int code, menunum, itemnum, subnum;
BOOL KeepGoing;
int c;
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)
	      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++ )
			SerIOPut( c );
		  }
		else if (character == '?')
		  {
		     PutString( "\x9b33m" ); /* in red */
		     PutString( VERSION );
 		     PutString( "\n\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 */
