/**********************************************************
 *                                                        *
 * Minimal Term for DNET (uc) Stone, SST                  *
 * Further modifications by Unknown, SST                  *
 * Based on FTerm by Matt Dillon                          *
 *                                                        *
 * ShellTerm [DNET_PORT] [NETWORK]                        *
 *                                                        *
 * 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!                        *
 * Fully public domain                                    *
 * Mega-thanx to Azza of the SST for hours of DNET source *
 * modification.... yes it compiles under SAS 6!          *
 **********************************************************/

#include "defs.h"

/* DEFAULT STRINGS */
char	*WHITE		= " \t\n\r",
		*EOLN		= "\n\r";

/* STANDARD FILE HANDLES */
long	out, err, in;

struct	AFileHandle	*ain, *aout, *alogfile;
char	overridepkt, prevmoderaw, rawlocalecho, moderaw, notdone;
long	lineend;
char	Buf[512], prg[40], logfilename[100];
void	*chan;
long	startmem;

extern struct IntuitionBase	*IntuitionBase;
extern struct GfxBase		*GfxBase;

#define SIGNALS (SIGBREAKF_CTRL_C | SIGBREAKF_CTRL_D | SIGBREAKF_CTRL_E | SIGBREAKF_CTRL_F)

/* standard in & out async file handles */
#define INBUFSIZE		500
#define WAIT_TIMEOUT	700000 /* uSeconds */


/* Do this on the next packet return */
#define IN_NULL			0
#define IN_WAIT			1
#define IN_CHARS		2
#define IN_RAWSET		3
#define IN_RAWCLR		4


/**********************************************************/
void  DoPacket(struct AFileHandle *afh, void *qbuf,
									long size, char pkttype)
/**********************************************************
     Send a packet according to type. qbuf & size may not
         be required (in fact only used for ARead)
 **********************************************************/
{
	switch(pkttype) {
		case IN_WAIT:
			ASendPacket(afh, ACTION_WAIT_CHAR, WAIT_TIMEOUT, 0, 0, 0); break;
		case IN_CHARS:
			ARead(afh, qbuf, size); break;
		case IN_RAWSET:
			ASendPacket(afh, ACTION_SCREEN_MODE, 1, 0, 0, 0); break;
		case IN_RAWCLR:
			ASendPacket(afh, ACTION_SCREEN_MODE, 0, 0, 0, 0); break;
	}
}

/**********************************************************/
short CmpString(char *cmd, char *table[], long n, long dist)
/**********************************************************
     Test a string against a table of string pointers,
  using every 'dist' pointer for a total of 'n' compares
                   return -1 on fail
         otherwise the index (ith compare ie 0, 1, 2 ... )
 **********************************************************/
{
short i, j;
	for(i = 0, j = 0; i < n; i++)
		if(table[j] && (strncmp(cmd, table[j], strlen(table[j])) == 0)) return(i);
		else j += dist;
	return(-1);
}

/**********************************************************/
char			*FindChar(char *string, char *instring)
/**********************************************************
  Find first character of 'string' contained in 'instring'
 **********************************************************/
{
short i, j;
	if(!(string && instring)) return(NULL);
	for(i = 0; string[i]; i++)
		for(j = 0; instring[j]; j++)
			if(string[i] == instring[j]) return(&string[i]);
	return(NULL);
}

/**********************************************************/
char			*SkipChar(char *string, char *instring)
/**********************************************************
Find first character of 'string' not contained in 'instring'
 **********************************************************/
{
short i, j;
	if(!(string && instring)) return(NULL);
	for(i = 0; string[i]; i++) {
		for(j = 0; instring[j] && (instring[j] != string[i]); j++);
		if(instring[j] == '\0') return(&string[i]);
	}
	return(NULL);
}

/* pass: rest of argline, command table, command count*4 */
typedef void *(* cmdfunc)(char *, char **, long);
/**********************************************************
                  Commands available
 **********************************************************/

/* NOTE!!! ARGUMENTS **MUST** FIT PROTOTYPE of cmdfunc!!!! */

void cmds_help(char *dummy, char *table[], long noofcmds)
{ short j;
	fpf(err,"Known commands:\n");
	for(j = 0; j < noofcmds; j += 4)
		if(table[j+1] && table[j])
			fpf(err, "%s\tor %s (%s)\n",table[j+1], table[j], table[j+2]);
		else if(table[j+1])
			fpf(err, "%s\t(%s)\n", table[j+1], table[j+2]);
		else if(table[j])
			fpf(err, "%s\t(%s)\n", table[j], table[j+2]);
}
void cmds_raw(void)
{ overridepkt = IN_RAWSET; }
void cmds_buf(void)
{ overridepkt = IN_RAWCLR; }
void cmds_lf(void)
{ lineend = 0x0a000000; }
void cmds_cr(void)
{ lineend = 0x0d000000; }
void cmds_lfcr(void)
{ lineend = 0x0a0d0000; }
void cmds_send(char *rest)
{ long n; if(rest && (n = strlen(rest))) DWrite(chan, rest, n); }
void cmds_flush(void)
{ DIoctl(chan, CIO_FLUSH, 0, 0); fpf(err,"\nFlushed\n"); }
void cmds_closelog(void)
{
	if(!alogfile) return;
	fpf(err,"'%s' ", logfilename);
	ASafeClose(&alogfile);
	fpf(err,"closed ok.\n");
}
void cmds_log(char *rest)
{
	cmds_closelog();
	if(!rest) return;
	spf(logfilename, sizeof(logfilename), rest, aout);
	fpf(err,"'%s' ", logfilename);
	if( !(alogfile = AEasyOpen(aout, rest, MODE_NEWFILE)) )
		fpf(err,"failed.\n"); else fpf(err,"opened ok.\n");
}
void cmds_execute(char *rest)
{
	if(!rest) return;
	fpf(err,"%s Executing '%s'\n", prg, rest);
	Execute(rest, NULL, err);
	fpf(err,"%s Terminating '%s'\n", prg, rest);
}
void cmds_localecho(void)
{ rawlocalecho = 1; }
void cmds_localechooff(void)
{ rawlocalecho = 0; }
void cmds_ctrlf(void)
{ char c = 6; DWrite(chan, &c, 1); }
void cmds_quit(void)
{ notdone = 0; }

/** ADD ANY NEW COMMANDS HERE */
static char *cmds[] = {
	"HELP","?","This message",				(char *)cmds_help,
	"RAW","R","Raw (normal terminal) mode",	(char *)cmds_raw,
	"BUF","B","Line buffered (shell) mode",	(char *)cmds_buf,
	"LFCR",NULL,"Linefeed & Carriage Return",(char *)cmds_lfcr,
	NULL,"LF","Linefeed",					(char *)cmds_lf,
	NULL,"CR","Carriage return",			(char *)cmds_cr,
	"SEND",NULL,"Send text",				(char *)cmds_send,
	"FLUSH","^F","Flush input",				(char *)cmds_flush,
	"EXECUTE","X","AmigaDOS command",		(char *)cmds_execute,
	"LOCALECHO","LE","Raw mode local echo on",(char *)cmds_localecho,
	"LOCALECHOOFF","LEO","Raw mode local echo off",(char *)cmds_localechooff,
	"LOG","L","Log to file",				(char *)cmds_log,
	"CLOSELOG","CL","Close log file",		(char *)cmds_closelog,
	NULL,"F","Send CTRL-F",					(char *)cmds_ctrlf,
	"QUIT", NULL, "Quit terminal",			(char *)cmds_quit
};
#define NOOFCMDS	((sizeof(cmds)/4)/4)

/**********************************************************/
void				*ParseCommand(char *cmd)
/**********************************************************
  Send this routine a command string and it will try and
               do something with it!
 **********************************************************/
{
char cmdbuf[400], *command, *rest;
short n;

/** KICK BACK INTO RAW IF WE WERE BEFORE (THO THIS MAY CHANGE) */
	overridepkt = prevmoderaw? IN_RAWSET : IN_NULL;

/** GET THE COMMAND */
	strncpy(cmdbuf, cmd, sizeof(cmdbuf)-1); cmdbuf[sizeof(cmdbuf)-1] = 0;
	command = SkipChar(cmdbuf, WHITE);
	if(!command) return(NULL);
	rest = FindChar(command, WHITE);
	if(rest) {
		char *temp = SkipChar(rest, WHITE);
		rest[0] = '\0'; rest = temp;
		if(rest) {
			temp = FindChar(rest, EOLN);
			if(temp) temp[0] = '\0';
		}
	}
	strupper(command);

/** DETERMINE THE COMMAND & DO THE CALL! */
	if((n = CmpString(command, cmds, NOOFCMDS, 4)) == -1 )
		if((n = CmpString(command, &cmds[1], NOOFCMDS, 4)) == -1 )
			{ fpf(err, "%s unrecognised\n", command); return(NULL); }
	fpf(err, "%s corresponds to %ld\n", command, n);
	return(((cmdfunc)cmds[(n<<2)+3])(rest, cmds, NOOFCMDS<<2));
}

/* call this function for each character */
typedef short (* mapfunc)(void *, char);

struct CMap {
	unsigned char *Map;		/* first 256 bytes is a table of offsets (/2) into next 510 bytes */
	short	MapSize;		/* Total in bytes */
	short	MapUsed;		/* Just of the seq part */
};

/**********************************************************/
void			CMapInit(struct CMap *cmap, short maxsize)
/**********************************************************/
{
short i;
	for(i = 0; i < cmap->MapSize; i++) (cmap->Map)[i] = 0xff;
	cmap->MapSize = maxsize; cmap->MapUsed = 0;
}


/**********************************************************/
void	CMapRemoveSeq(struct CMap *cmap, char c)
/**********************************************************/
{	short i, k, l, o;
	if((k = (cmap->Map)[c]) == 0xff) return;
	(cmap->Map) = -1;
	for(i = 0; i < 256; i++) if((cmap->Map)[i] == k) return;
	l = ((cmap->Map)[o]+2) >> 1;
	for(i = 0; i < 256; i++)
		if((cmap->Map)[i] > k) cmap->Map[i] -= l;
	l += l; o = 256 + k + k;
	cmap->MapUsed -= l;
	dbmov(&(cmap->Map)[o+l], &(cmap->Map)[o], cmap->MapSize - (o+l));
}

/**********************************************************/
short	CMapAddSeq(struct CMap *cmap, char c, char *seq)
/**********************************************************/
{
short i, j, l, o, k;
	if((cmap->Map)[c] != 0xff) CMapRemoveEntry(c, cmap);
	l = strlen(seq); o = (l+2) & 0xfffe;
	k = (cmap->MapUsed+1) & 0xfffe;
	if(o + k + 256 > cmap->MapSize) return(0);
	cmap->MapUsed += m;
	(cmap->Map)[c] = k >> 1;
	(cmap->Map)[k+256] = l;
	dbmov(&((cmap->Map)[k+256+1]), seq, l);
}

/**********************************************************/
long MapText(struct CMap *cmap, mapfunc fn, void *fn_data,
							char buffer[], short n)
/**********************************************************
   Entries with a '-1' in the character map are unchanged
       otherwise the value is used as an offset << 1
        into the seqbase map for a complete sequence
      which has the sequence length as the first byte
 **********************************************************/
{
short i, k, l;
long w;
	for(i = 0; i < n; i++)
		if((k = cmap->Map[buffer[i]]) == 0xff)
			{ if(!fn(fn_data, buffer[i])) return(w); else w++; }
		else for(k += 256 + k, l = cmap->Map[k++]; k < l; k++)
			{ if(!fn(fn_data, cmap->Map[k])) return(w); else w++; }
	return(w);
}


struct InterData {
	char	*buffer; short buffersize, position;
	void	*channel;
};
short DNet_DoWrite(InterData *data, char c)
{	data->buffer[data->position++] = c;
	if(data->position > data->buffersize)
		DWrite(data->channel, data->position, data->buffer);
}

void WriteText(void *dchan, struct CMap *cmap, char *buffer, short n)
{
char buf[256];
struct InterData data;
	data.buffer = buf;
	data.buffersize = sizeof(buf);
	data.position = 0;
	data.channel = dchan;
	if(rawlocalecho && moderaw)
		{ AWrite(aout, buffer, n); if(alogfile) AWrite(alogfile, buffer, n); }
	MapText(DNet_DoWrite, &data, buffer, n, cmap);
	if(data.position) DWrite(data.channel, data.position, data.buffer);
}

/**********************************************************/
             int main(int argc, char **argv)
/**********************************************************
				Do the minimal term stuff!
		Wait on our message ports for incoming
 **********************************************************/
{
long	amask, dmask, mask;
char	gettingoptions = 0,
		*hostname = NULL,
		inbuf[INBUFSIZE],
		currpkt, scriptmode;


UWORD	port	=	PORT_IALPHATERM;
struct	Process *us;
	startmem = AvailMem(MEMF_PUBLIC);

/** STUFF WE NEED */
	out = Output(); in = Input(); err = Open("*", MODE_OLDFILE);
	us = (struct Process *)FindTask(NULL); SetSignal(0,SIGNALS);
	spf(prg, sizeof(prg), "%s [%s %ld]", argv[0], us->pr_Task.tc_Node.ln_Name, us->pr_TaskNum);
    IntuitionBase = (struct IntuitionBase *)OpenLibrary("intuition.library", 0);
    GfxBase = (struct GfxBase *)OpenLibrary("graphics.library", 0);


/** PRINT USAGE */
	fpf(out,"ShellTerm for DNET (uc) Stone, SST\n"
		"email: c9107253@mystra.newcastle.edu.au or c9107253@cs.newcastle.edu.au\n"
		"\nCTRL-\\ or CTRL-F then CTRL-C to quit, CTRL-F for options\n\n");
	if( argc<2 )
		fpf(err,"(Use: %s [>file] [<file] [DNET_PORTNUMBER] [NETWORK])\n", argv[0]);


/** PARSE COMMAND LINE */
	if(argc > 1) port = (UWORD) atoi(argv[1]);
	if(argc > 2) hostname = argv[2];


/** GET ASYNC INPUT & OUTPUT HANDLES ON DIFFERENT PORTS */
	if(! (ain = AEasyMakeFD2AFD(NULL, in)))
		{ fpf(err, "No input!?\n"); goto e3; }
	if(! (aout = AEasyMakeFD2AFD(NULL, out)))
		{ fpf(err, "No output!?\n"); goto e3; }
	if(!IsInteractive(((long)(ain->FileHandle))>>2))
		{ fpf(err, "Warning: input is not of console type\n"); }
	DoPacket(ain, 0, 0, currpkt = IN_WAIT);
	overridepkt = IN_NULL;
	cmds_lf();
	cmds_localechooff();
	moderaw = 0;


/** DO THAT FUNKY DNET STUFF */
	fpf(out,"Opening port %ld on network %s ",port,hostname?hostname:"0");
    if( ! (chan = DOpen(hostname, port, 20, 15)) )
		{ fpf(err,"Sorry, Unable to connect\n"); goto e3; }
    DQueue(chan, 32);
	DIoctl(chan, CIO_SETROWS, 25, 0); DIoctl(chan, CIO_SETCOLS, 80, 0);
	fpf(out,"connected\n");


/** KEEP ON LOOPING */
    dmask   = 1 << ((PORT *)chan)->mp_SigBit;
	amask	= 1 << ASIGBIT(ain);
	for (notdone = 1; notdone;) {

		mask = Wait(amask | dmask | SIGNALS);

	/** CHECK ASYNC INPUT FROM USER */
		if (mask & amask) {
			long n; struct AFileHandle *currafh;

		/** BE *VERY* CAREFUL ON WHERE WE MAY "ACCIDENTALLY" GETMSG */
			while(currafh = AGetReply(ain->APort)) {
				n = AResult(currafh);
				if(currafh == ain) {
				/** CHECK REPLY OF PACKET */
					switch(currpkt) {
						case IN_WAIT:
							currpkt = n? IN_CHARS: IN_WAIT;
							break;
						case IN_CHARS:
							currpkt = IN_WAIT;
							if(!n) notdone = 0;
							else if(n > 0) {
								if(!gettingoptions)
									WriteText(chan, inbuf, n, (char *)&lineend);
								else { ParseCommand(inbuf); gettingoptions = 0; }
							}
							break;
						case IN_RAWSET:
							currpkt = IN_WAIT;
							if(!n) fpf(err, "Couldn't set terminal mode RAW\n");
							else moderaw = 1;
							break;
						case IN_RAWCLR:
							currpkt = IN_WAIT;
							moderaw = 0;
							break;
					}
				/** SEND OUT NEXT PACKET (NOW CURRENT PACKET) */
					if(overridepkt) currpkt = overridepkt;  overridepkt = 0;
					DoPacket(currafh, inbuf, sizeof(inbuf)-4, currpkt);

				} /* else OTHER INPUT FILES (others?? nah!) */
			}
		}


	/** CHECK DNET */
		if (mask & dmask) {
			long n;
			if ((n = DNRead(chan, Buf, sizeof(Buf))) > 0) {
				/* WE GOT SOME TEXT! PRINT TO STD OUTPUT! */
				AWrite(aout, Buf, n);
				if(alogfile) AWrite(alogfile, Buf, n);
			} else if (n == -2) {
				short val, cmd; char aux;
				cmd = DGetIoctl(chan, &val, &aux);
				switch(cmd) {
					case CIO_MODE:
						overridepkt = val ? IN_RAWCLR: IN_RAWSET; break;
					case CIO_SETROWS:
						break;
					case CIO_SETCOLS:
						break;
				}

			} else if (n < 0) cmds_quit();
		}


	/** CHECK SIGNALS (AND SEND THEM PERHAPS) */
		if(mask & SIGBREAKF_CTRL_C) {
			if(gettingoptions) cmds_quit();
			else { char c = 3; DWrite(chan, &c, 1); }
		}

		if(mask & SIGBREAKF_CTRL_D)
			{ char c = 4; DWrite(chan, &c, 1); }

		if(mask & SIGBREAKF_CTRL_E)
			{ char c = 5; DWrite(chan, &c, 1); }

		if(mask & SIGBREAKF_CTRL_F) {
			if(gettingoptions) {
				cmds_flush();
				gettingoptions = 0;
			} else {
				fpf(err,"\n%s   ? <return> for help\n", prg);
				if(prevmoderaw = moderaw) overridepkt = IN_RAWCLR;
				gettingoptions = 1;
			}
		}
	}


/** END ALL THE PAIN */
e3:
	if(ain && moderaw) {
		fpf(err,"\nChanging mode back to normal");
		AResultWait(ain);
		DoPacket(ain, 0, 0, IN_RAWCLR); }

	fpf(err,"\n%s Closing channel. ", prg);
	if(chan) DClose(chan);
/** CLOSE ALL FILES ON THE 2 PORTS */
	fpf(err,"output. ");
	ADeletePort(aout->APort);
	fpf(err,"input. ");
	ADeletePort(ain->APort);
	fpf(err,"done!\n");
	if(err) Close(err);
	CloseLibrary((LIB *)IntuitionBase);
	CloseLibrary((LIB *)GfxBase);
	return(0);
}

/* to add:

arexx support
text message support

*/
