/**********************************************************
 *                                                        *
 * Minimal Term for DNET (uc) Stone, SST                  *
 * Further modifications by Unknown, SST                  *
 * pterm name startupfile                                 *
 * the startup file is used as tho input had come from    *
 * stdin (ie keyboard or pipe or what ever)               *
 *                                                        *
 * Set ya TABS to 4!                                      *
 * Email me!! c9107253@mystra.newcastle.edu.au            *
 * For any reason!! (eg DNet, AmiNet, Demos, amigas...)   *
 * Please send any changes U make!                        *
 *                                                        *
 * Breaks out of search for port with ctrl-D              *
 * all others kill program                                *
 *                                                        *
 * Mega-thanx to Azza of the SST for hours of DNET source *
 * modification.... yes it compiles under SAS 6!          *
 **********************************************************/

/* Multi input system: this one has input from shell */


#include <exec/types.h>
#include <exec/memory.h>
#include <exec/ports.h>
#include <dos/dos.h>
#include <proto/exec.h>
#include <proto/dos.h>
#include <string.h>

#include <st/textmessage.h>
#include <st/st_proto.h>

#define DEFHELPCHAR '?'
#define EOLN		13,10
#define	ctrlSEQ		'^'
#define	ctrlLEN 	1

#define cmdSEQ		'\\'
#define cmdLEN		1


#define NOPORTDELAY	60	/* delay when checking for non-existant port */

/* THese are the CTRL-Signals ONLY! */
#define SIGNALS (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F)

char 	things0[]	= { 10, 13, 0 },
		things1[]	= { 10, 0 },
		things2[]	= { 13, 0 },
		things3[]	= { 8, 0 },
		things4[]	= { 13, 10, 0 },
		*ctrlWHITE	= "^\n\r ",
		*WHITE		= " \t\n\r",
		*eolntok	= "\n\r";

/* Every second one is the command command. Send \ followed by command*/

char *cmds[] = {
 "LFCR",	things0,	"LF & CR at transmit end of line",
 "CRLF",	things4,    "CR & LF at end of line",
 "LF",		things1,	"LF end of line",
 "CR",		things2,	"CR end of line",
 "BS",		things3,	"back space character",
 "PROMPTOFF", NULL,		"main prompt off",
 "PROMPT",	NULL,		"main prompt on",
 "PORT",	NULL,		"new destination port (eg \p mystra1)",
 "P",		NULL,		"new destination port (eg \p mystra1)",
 "AUTOKILLOFF",NULL,   	"don't kill server with exit",
 "AUTOKILL"   ,NULL,   	"kill server with exit (\q kills us & server)",
 "AUTOEXITOFF",NULL, 	"don't exit when destination port not found",
 "AUTOEXIT"   ,NULL,	"auto exit when destination port not found",
 "WAITFORPORTOFF", NULL,"ignore input if no dest port",
 "WAITFORPORT",NULL,	"wait for dest port to appear when sending",
 "EXECUTE", NULL,       "send a command to the AmigaShell",
 "X", NULL,				"send a command to the AmigaShell",
 "QUOTE",	NULL, 		"send string as command to server",
 "I",		NULL,		"Ask server for help/info",
 "H",		NULL,		"Ask server for help/info",
 "Q",		NULL, 		"Quit/exit this p-term",
 "E",		NULL,  		"Quit/exit this p-term"
};

/* if there are common parts of words (eg promptoff & prompt) makesure
   the larger (promptoff) comes before */

#define CMDDIST 3							/* strings/command */

#define CMD_LFCR			0
#define CMD_CRLF			1
#define CMD_LF				2
#define CMD_CR				3
#define CMD_BS				4
#define CMD_PROMPTOFF		5
#define CMD_PROMPT			6
#define CMD_PORT			7
#define CMD_P				8
#define CMD_AUTOKILLOFF		9
#define CMD_AUTOKILL		10
#define CMD_AUTOEXITOFF		11
#define CMD_AUTOEXIT		12
#define CMD_WAITFORPORTOFF	13
#define CMD_WAITFORPORT		14
#define CMD_EXECUTE			15
#define CMD_X				16
#define CMD_QUOTE			17
#define CMD_I				18
#define CMD_H				19
#define CMD_Q				20
#define CMD_E				21
#define CMDCNT				22

/* port-terminal attributes & characteristics */
/* these may be modified at anytime */
#define MAXPNAMESIZE 99


/* Return codes of functions */
#define RET_QUIT	0
#define RET_OK		0x0001
#define RET_CTRLC	0x0f00
#define RET_CTRLD	0x0f10
#define RET_CTRLE	0x0f20
#define RET_CTRLF	0x0f30
#define RET_HALTC	0x0e00
#define RET_HALTD	0x0e10
#define RET_HALTE	0x0e20
#define RET_HALTF	0x0e30
#define RET_NOPORT	0x0002
#define RET_NOTHING 0x0003

char 			eoln[10] = { EOLN,0 },
				ctrlseq[10] = { ctrlSEQ,0 }, ctrllen = ctrlLEN,
				cmdseq[10] = { cmdSEQ,0 }, cmdlen = cmdLEN,
				helpchar = DEFHELPCHAR,

				*prompt = "%s > ",
				mainprompt = 0,
				autoexit = 0,
				waitport = 0,
				autokillserver = 0,
				destportname[MAXPNAMESIZE + 2],

				stringbuf[400],
				*progname;

/* Standard Output() */
long			output;

/*********************************************************/
long Readln(BPTR file, char *buffer, long max)
/*********************************************************
           Attempt to read a line of stuff.
            return 0 on the end of a file
Uses the nasty method of reading one character at a time
(sorry) throws away line on eof
**********************************************************/
{
long i = 0;
	do {
		if(!Read(file, &buffer[i], 1))
				return(0);
	} while(!charinstr(buffer[i++], eolntok) && (i < max-1) );
	buffer[i] = '\0';
	return(i);
}



/*********************************************************/
BOOL SafePutToPort(struct Message *message, char *portname)
/*********************************************************
            Only put to the port if it exists!
           Forbid first then test for the port
           Return NULL if port couldn't be found
**********************************************************/
{
struct MsgPort *port;

    Forbid();
	if (port = FindPort(portname)) PutMsg(port,message);
    Permit();
    return((BOOL)port); /* If zero, the port has gone away */
}


/*********************************************************/
short SendTextMessage(char *portname,
		struct TextMessage *txtmsg, struct MsgPort *ourport)
/*********************************************************
  Try to send a text message to a port of name "name"
         Breaks sent to us cause us to quit
**********************************************************/
{
ULONG	mask, pmask = 1 << ourport->mp_SigBit;
struct TextMessage *currtxtmsg;

	/* just in case our replier doesn't */
	AllocString0(&(txtmsg->RepString),0);

	while(! SafePutToPort((struct Message *)txtmsg,portname) ) {
		fpf(output,"Destination port not found (CTRL-C to ignore)..\n");
		if(autoexit) return(RET_QUIT);
		if(!waitport) return(RET_NOPORT);
		/* Where is that port??? Might hang around... just in case. */
		Delay(NOPORTDELAY);
		mask = SetSignal(0,SIGNALS);	/* Set all CTRL signals to 0 */
		/* If we got a control signal... quit */
		     if(mask & SIGBREAKF_CTRL_C) return(RET_HALTC);
		else if(mask & SIGBREAKF_CTRL_D) return(RET_HALTD);
		else if(mask & SIGBREAKF_CTRL_E) return(RET_HALTE);
		else if(mask & SIGBREAKF_CTRL_F) return(RET_HALTF);
	}

	/* now we must wait for the reply! */
	for(;;) {
		mask = Wait(pmask | SIGNALS);
		if(mask & pmask) {
			while (currtxtmsg =
				(struct TextMessage *) GetMsg(ourport)) {

				/* Write out contents of the reply string if need be */
				if(currtxtmsg->RepString.String) {
					Write(output, currtxtmsg->RepString.String,
										STRLEN(&(currtxtmsg->RepString)));
					SafeFreeString0(&(currtxtmsg->RepString));
				}

				/* Got our funky text back */
				return(RET_OK);
			}
		}
		if((mask & SIGBREAKF_CTRL_C) ||
		  (mask & SIGBREAKF_CTRL_D) ||
		  (mask & SIGBREAKF_CTRL_E) ||
		  (mask & SIGBREAKF_CTRL_F) ) {
				fpf(output,"Sorry,  waiting for a reply from '%s'\n",portname);
		}
	}
}


/*********************************************************/
short DoCmdSendTM(char *string, char *send,
		struct TextMessage *msg, struct MsgPort *ourport)
/*********************************************************
 Pass a string with a user command on it and this will try
    	        to do stuff from that!
	  modifies terminal characteristics & the like
  if command is unknown pass first 120 bytes of string
                      to server
  returns      1 if textmessage should be sent
			OR 0 if unknown command
			OR -1 if a QUIT command was received
**********************************************************/
{
char	*argument, *command, *rest, backup[120];
short	i;

	strncpy(backup,string,119);
	backup[119] = '\0';
	command = strtok(string,WHITE);
	rest = strtok(NULL,NULL);
	strupper(command);

	for(i = 0; i < CMDCNT*CMDDIST; i += CMDDIST) {
		if(strncmp(command,cmds[i],strlen(cmds[i])) == 0) {
			switch((i+CMDDIST-1)/CMDDIST) {
				case CMD_LFCR:
				case CMD_CRLF:
				case CMD_LF:
				case CMD_CR:
					strcpy(eoln,cmds[i+1]);
					fpf(output,"\nSending %s\n",cmds[i+2]);
					return(RET_OK);

				case CMD_BS:
					strcpy(send,cmds[i+1]);
					msg->String.StrLen = -1;
					msg->String.FreeLen = 0;
					msg->String.String = send;
					msg->Command = TM_TEXT;
					fpf(output,"\nSending %s\n",cmds[i+2]);
					return(SendTextMessage(destportname,msg,ourport));

				case CMD_PROMPT:
					mainprompt = -1;
					fpf(output,"\n%s\n",cmds[i+2]);
					return(RET_OK);

				case CMD_PROMPTOFF:
					mainprompt = 0;
					fpf(output,"\n%s\n",cmds[i+2]);
					return(RET_OK);

				case CMD_PORT:
				case CMD_P:
					argument = strtok(rest,WHITE);
					if(argument == NULL) {
						fpf(output,"\nUSE: %s %s (currently port is '%s')\n",
							cmds[i],cmds[i+2],destportname);
					} else {
						fpf(output,"\nPort is now '%s' (Old port was '%s')\n",
							argument,destportname);
						strncpy(destportname,argument,MAXPNAMESIZE);
					}
					return(RET_OK);

				case CMD_AUTOKILL:
					autokillserver = 1;
					return(RET_OK);

				case CMD_AUTOKILLOFF:
					autokillserver = 0;
					return(RET_OK);

				case CMD_AUTOEXIT:
					autoexit = 1;
					return(RET_OK);

				case CMD_AUTOEXITOFF:
					autoexit = 0;
					return(RET_OK);

				case CMD_WAITFORPORT:
					waitport = 1;
					return(RET_OK);
				case CMD_WAITFORPORTOFF:
					waitport = 0;
					return(RET_OK);

				case CMD_QUOTE:		/* send out a command to receiver */
					fpf(output,"Sending command '%s'\n",rest);
					strcpy(send,rest);
					msg->String.StrLen = -1;
					msg->String.FreeLen = 0;
					msg->String.String = send;
					msg->Command = TM_COMMAND;
					return(SendTextMessage(destportname,msg,ourport));

				case CMD_X:
				case CMD_EXECUTE:	/* Send command to amiga dos! */
					fpf(output,"Executing '%s'\n",rest);
					Execute(rest, NULL, output);
					return(RET_OK);

				case CMD_I:
				case CMD_H:
					msg->String.StrLen = 0;
					msg->String.FreeLen = 0;
					msg->String.String = NULL;
					msg->Command = TM_HELP;
					fpf(output,"\nAsking for server commands\n");
					return(SendTextMessage(destportname,msg,ourport));

				case CMD_Q: case CMD_E:
					if(autokillserver) {

						msg->String.StrLen = 0;
						msg->String.FreeLen = 0;
						msg->String.String = NULL;
						msg->Command = TM_EXIT;
						waitport = 0;	/* Make sure we don't stick around */
						SendTextMessage(destportname,msg,ourport);
					}
					return(RET_QUIT);

			}
		}
	}

/* otherwise send off the command to the server - we couldn't interpret */
	msg->String.StrLen = -1;
	msg->String.FreeLen = 0;
	msg->String.String = backup;
	msg->Command = TM_COMMAND;
	return(SendTextMessage(destportname,msg,ourport));

}


/*********************************************************/
short DoInput(char *instring,
		struct TextMessage *txtmsg, struct MsgPort *ourport)
/*********************************************************
		string buf is usually trashed after this!
**********************************************************/
{
char	*temppos;
short	i;

	/* DEFAULT VALUES FOR THE TEXT MESSAGE */
	txtmsg->Extra = NULL;
	for(i = 0; i < 8; txtmsg->Args[i++] = 0);

	/* CHECK FOR THE COMMAND SEQUENCE (THAT'S A '\' ON A NEW LINE) */
	if( strncmp(instring,cmdseq,cmdlen) == 0) {

		while(strncmp(instring,cmdseq,cmdlen) == 0) instring += cmdlen;

		/* PRINT HELP TO THE USER */
		if(*instring == helpchar) {
			fpf(output,"\nCharacter sequences:\n");
			for(i = 0; i< CMDCNT*CMDDIST; i += CMDDIST) {
				fpf(output,"\\%s\t:%s\n",cmds[i],cmds[i+2]);
			}
		}
		else if(*instring)
			return(DoCmdSendTM(instring, instring, txtmsg, ourport));
		else return(RET_NOTHING);
	}

	/* SEND CONTROL CHARACTERS? */
	else if( strncmp(instring,ctrlseq,ctrllen) == 0) {
		while(strncmp(instring,ctrlseq,ctrllen) == 0)
				instring += ctrllen;
		strupper(instring);

		/* 'a'-> 1, 'b' -> 2, 'c' -> 3 etc */
		for(temppos = instring;
				*temppos && !charinstr(*temppos,eoln); temppos++)
						*temppos -= ('A' - 1);
		if(temppos != instring)
				fpf(output,"\nSending control characters!\n");
		*temppos = '\0';

	} else
		strcat(instring,eoln);

	if( txtmsg->String.StrLen	=	strlen(instring) ) {
		txtmsg->String.String	=	instring;
		txtmsg->String.FreeLen  =	0;
		txtmsg->Command			=	TM_TEXT;
		return(SendTextMessage(destportname, txtmsg, ourport));
	}

	return(RET_NOTHING);
}

/*********************************************************/
		int main(int argc, char **argv)
/*********************************************************/
{
struct	MsgPort *ourport = 0;
struct	TextMessage *txtmsg = 0;

BPTR	startup, input;

	output = Output();
	input = Input();

	fpf(output,"PTERM Terminal end shell version [shell->port] (uc) Stone, SST\n"
			   "email: c9107253@mystra.newcastle.edu.au (1993)\n");

	if( argc<2 ) {
		fpf(output,"Use: %s [>file] [<file] portname [startupfile]\n"
				   "(This portname has NOTHING to do with the DNET ports)\n",argv[0]);
		return(5);
	}

	progname = argv[0];
	strncpy(destportname,argv[1],MAXPNAMESIZE + 1);
		/* Make sure completely padded with \0 */

	fpf(output,"Sending lines to port '%s'\n"
				"Current control character prefix is '%s'\n"
				"'%s%lc' for help!\n\n", destportname, ctrlseq, cmdseq, helpchar);

	if( !(ourport = CreatePort(0,0)) ) {
		fpf(output,"Reply port will not open!\n");
		goto enditnow;
	}

    if( !(txtmsg = (struct TextMessage *)
			AllocMem(sizeof(struct TextMessage), MEMF_PUBLIC)) ) {
		fpf(output,"Sorry.. Not enough memory for message structure\n");
		goto enditnow;		
    }

/* WRITE MESSAGE DEFAULTS? */
    txtmsg->Msg.mn_Node.ln_Type	= NT_MESSAGE;
    txtmsg->Msg.mn_Length		= sizeof(struct TextMessage);
    txtmsg->Msg.mn_ReplyPort	= ourport;


/* READ A STARTUP FILE? */
	if(argc >= 3) {
		if(startup = Open(argv[2],MODE_OLDFILE)) {
			while(	Readln(startup, stringbuf, 300) ) {
				if(!strtok(stringbuf, eolntok))
					stringbuf[0] = '\0';
				if( DoInput(stringbuf, txtmsg, ourport) == RET_QUIT)
					goto enditnow;
			}
			Close(startup);
		}
		else
			fpf(output,"Startup file '%s' not found.\n",argv[2]);
	}

/* READ WHAT STDIN HAS TO GIVE US */
	do {
		if(mainprompt) fpf(output,prompt,destportname);
		if(! Readln(input, stringbuf, 300) ) {
			fpf(output,"\nEOF\n");
			goto enditnow;
		}
		if(!strtok(stringbuf, eolntok))		/* remove eoln characters */
				stringbuf[0] = '\0';

	} while(DoInput(stringbuf, txtmsg, ourport) != RET_QUIT);

enditnow:
	if(ourport) {
		Forbid();
		/* There should be no pending messages... if there are then
			something is very wrong and the system is probably stuffed.. */
		while((struct TextMessage *) GetMsg(ourport));
		DeletePort(ourport);
		Permit();
	}

	if(txtmsg) FreeMem(txtmsg,sizeof(struct TextMessage));
	return(0);
}

