/*****************************************************************************
	ZAmiga.c
	System dependent code for Amiga (SAS/C 5.10)
	This file created by Ben Mesander, ben@piglet.cr.usgs.gov, by
	starting with zunix.c and making the necessary changes.

*****************************************************************************/
/*
 * RCS information
 *
 * $Header: Elros:src/c/teco/zamiga.c,v 1.4 91/01/26 22:40:31 ben Exp Locker: ben $
 *
 * $Log:	zamiga.c,v $
 * Revision 1.4  91/01/26  22:40:31  ben
 * Removed possible recursive call of TAbort() in function sendpkt().
 * 
 * Revision 1.3  91/01/26  22:21:51  ben
 * Cleaned up program exit logic:
 * changed calls to ZExit() to TAbort().
 * 
 * Revision 1.2  91/01/26  21:23:12  ben
 * Added RCS information
 * 
 */

/*
 * Define standard functions. 
 */

#include <errno.h>		/* define errno */
#include <signal.h>		/* to catch ^C signal */
#include <stdio.h>		/* define stdin */
#include <string.h>		/* strncpy(), strlen(), etc. */
#include <time.h>		/* stuff for time commands */
#include <fcntl.h>		/* for isatty() call */

/*
 * Stuff used to fake a tempnam() call.
 */
char	*tempnam();		/* not in Amiga libraries */

/*
 * Amiga-specific stuff
 */

#include "exec/types.h"
#include "exec/ports.h"
#include "exec/io.h"
#include "exec/memory.h"
#include "libraries/dos.h"
#include "libraries/dosextens.h"
#undef GLOBAL		/* name conflict here, doesn't matter */

#define MAXARGS 7L	/* limit in packet structure (dosextens.h) */
#define NARGS	1L	/* number of arguments */
#define ESC    27L

#include "zport.h"		/* define portability identifiers */
#include "tecoc.h"		/* define general identifiers */
#include "clpars.h"		/* command-line parsing macro */
#include "dchars.h"		/* define identifiers for characters */
#include "deferr.h"		/* define identifiers for error messages */
#include "defext.h"		/* define external global variables */
#include "dscren.h"		/* define identifiers for screen i/o */

extern	int sys_nerr;		/* number of system error messages */
extern	char *sys_errlist[];	/* error message text */
static	BOOLEAN tty_set = FALSE;	/* Has the terminal been set? */

/*****************************************************************************
	IFiles holds the file data blocks for input files.  There are three
static input streams:   the primary input stream,  the secondary input stream,
and the input stream used by the EQq command.  To access these three files,
identifiers defined in file tecoc.h are used to index into this array.
Other elements of this array are used to access input files for the EI
command.
*****************************************************************************/

FILE *IFiles[NIFDBS];

/*****************************************************************************
	OFiles holds the file data blocks for the output files.  There are
three output streams:   the primary output stream,  the secondary output
stream and the output stream used by the E%q command.  The array is indexed
using identifiers defined in file tecoc.h.
*****************************************************************************/

static struct {
	char	OFNam[FILENAME_MAX];	/* output file name */
	char	OTNam[FILENAME_MAX];	/* temporary output file name */
	FILE	*OStrem;		/* stream */
} OFiles[NOFDBS];


/*****************************************************************************

	sendpkt()

	This function sends a message packet to an AmigaDOS handler

	It is used in teco to send control messages to the console -
	(turn on and off echo of characters)

*****************************************************************************/

static long sendpkt(pid,action,args,nargs)

struct MsgPort *pid;	/* process indentifier ... (handlers message port ) */
long action,           /* packet type ... (what you want handler to do )   */
     args[],          /* a pointer to a argument list */
     nargs;          /* number of arguments in list  */
{
  
 struct MsgPort        *replyport;
 struct StandardPacket *packet;
 
 long   count, *pargs, res1; 

 if(nargs > MAXARGS) ZExit(EXIT_FAILURE); /* not TAbort due to possible loop */
 
 replyport = (struct MsgPort *) CreatePort(NULL,NULL); /* make reply port */
 if(!replyport) return(NULL);

 packet = (struct StandardPacket *) 
   AllocMem((long)sizeof(*packet),MEMF_PUBLIC | MEMF_CLEAR);
 if(!packet) 
   {
    DeletePort(replyport);
    return(NULL);
   }

 packet->sp_Msg.mn_Node.ln_Name = (char *) &(packet->sp_Pkt); /* link packet- */
 packet->sp_Pkt.dp_Link         = &(packet->sp_Msg);        /* to message    */
 packet->sp_Pkt.dp_Port         = replyport;         /* set-up reply port   */
 packet->sp_Pkt.dp_Type         = action;           /* what to do... */

/* move all the arguments to the packet */
 pargs = &(packet->sp_Pkt.dp_Arg1);        /* address of first argument */
 for(count=NULL;count < nargs;count++) 
   pargs[count]=args[count];

 PutMsg(pid,packet); /* send packet */

#if DEBUGGING
  printf("Waiting for packet...\n"); 
#endif

 WaitPort(replyport); /* wait for packet to come back */
 GetMsg(replyport);   /* pull message */

 res1 = packet->sp_Pkt.dp_Res1; /* get result */

/* all done clean up */
 FreeMem(packet,(long)sizeof(*packet)); 
 DeletePort(replyport); 

 return(res1);   
  
}


/*****************************************************************************

  Non-ANSI-contemptible tempnam() call. Would that the SAS Institute ever
  really live up to their promise of an "ANSI-compatible" library.  Sigh.

*****************************************************************************/

static char *tempnam(dir,pfx)
char	*dir, *pfx;
{
	char *s;
	long timeVal;
	char timeAry[4];
	char tBuf[7];
	struct tm *ut;

	s = (char *)calloc(strlen(dir)+strlen(pfx)+8, sizeof(char));

	/* pathological case */
	if (s==NULL) return s;

	/* get current time to make a unique filename */
	
	timeVal = time(NULL);
	ut = localtime(&timeVal);
	timeAry[0] = (unsigned char)ut->tm_hour;
	timeAry[1] = (unsigned char)ut->tm_min;
	timeAry[2] = (unsigned char)ut->tm_sec;
	timeAry[3] = (unsigned char)0;

	stptime(tBuf, 1, timeAry);
	
	return(strcat(strcat(strcat(s,dir),pfx),tBuf));
}


/*****************************************************************************

	ZErMsg()

	This function displays error message from the operating system on
the terminal screen.  The error message text is retrieved from the operating
system and imbedded in a TECO-style message with the SYS mnemonic.

*****************************************************************************/

static VVOID ZErMsg()
{
	if (errno < sys_nerr)
		ErrStr(ERR_SYS, sys_errlist[errno]);
}

/*****************************************************************************

	ZAlloc()

	This function allocates memory.  The single argument is the number of
bytes to allocate.  TECO-C uses the ZFree and ZRaloc functions to de-allocate
and re-allocate, respectively,  the memory allocated by this function.

*****************************************************************************/

voidptr ZAlloc(MemSize)		/* allocate memory */
SIZE_T MemSize;
{
	return (voidptr *)malloc(MemSize);
}

/*****************************************************************************

	ZBell()

	Thus function rings the terminal bell.  For most platforms,  this
means just writing a bell character (control-G) to the terminal.  An amusing
hack makes the Amiga SAY 'ding' out loud whenever a bell is hit...

*****************************************************************************/

VVOID ZBell(VVOID)
{
	FILE	*fp;
	
	if ((fp=fopen("SPEAK:","w"))==NULL) {
/*
 * can't open speak device, just flash the screen
 */
		ZDspCh('\7');
	} else {
/*
 * say 'ding'.
 */
		fputs("ding",fp);
		fclose(fp);
	}
}

/*****************************************************************************

	ZChIn()

	This function inputs a single character from the terminal.

	1.  the character is not echoed on the terminal
	2.  ^C calls an interrupt routine.  Note that this must be
	    implemented so that a ^C will cancel a current output via
	    ZDspBf.  The ^C must be a true interrupt.
	3.  type-ahead is always nice
	4.  The character must be returned immediately:  no fooling
	    around waiting for a carriage-return before returning.
	5.  If the NoWait argument is TRUE, don't wait.
	6.  When the user hits the RETURN key,  TECO is supposed to see
	    a carriage return and then a line feed.  The function must
	    deal with this by returning a carriage return to the caller
	    and then "remembering" to send a line feed on the next call.
	7.  handle ET_BKSP_IS_DEL flag

*****************************************************************************/

DEFAULT ZChIn(NoWait)			/* input a character from terminal */
BOOLEAN NoWait;				/* return immediately? */
{
	unsigned char Charac;
	static	BOOLEAN	LastLF = FALSE;

	if (LastLF) {
		LastLF = FALSE;
		return (DEFAULT)LINEFD;
	}
	if (read(fileno(stdin), &Charac, 1) != 1) {
		if (!GotCtC) {
			ZErMsg();
			ErrMsg(ERR_URC);
			exit(EXIT_FAILURE);
		}
	}
	if (Charac == CRETRN) {
		LastLF = TRUE;
		return (DEFAULT)CRETRN;
	}
	if (EtFlag & ET_BKSP_IS_DEL) {
		if (Charac == DELETE) {
			Charac = BAKSPC;
		} else if (Charac == BAKSPC) {
			Charac = DELETE;
		}
	}
	return (DEFAULT)Charac;
}

/*****************************************************************************

	ZClnEG()

	This function executes a :EG command.  The :EG commands are
used to get access to operating system functions.  The minimum set of
functions is

	:EGINI$		gets, sets or clears the initialization file name
	:EGMEM$		gets, sets or clears the file name memory
	:EGLIB$		gets, sets or clears the macro library directory
	:EGVTE$		gets, sets or clears the video macro file name

	although more functions may be defined.

*****************************************************************************/

LONG ZClnEG(EGWhat,EGOper,TxtPtr)
DEFAULT EGWhat;			/* what to get/set/clear: MEM, LIB, etc. */
DEFAULT EGOper;			/* operation: get, set or clear */
charptr TxtPtr;			/* if setting,  value to set */
{
	DBGFEN(1,"ZClnEG",NULL);
	DBGFEX(1,DbgFNm,"0");
	return 0;		/* :EG is unsupported */
}

/*****************************************************************************
  See the definition of MEMMOVE in ZPORT.H for a description of this
  function.
*****************************************************************************/

VVOID ZCpyBl(Destin, Source, Length)
charptr Destin;
charptr Source;
SIZE_T Length;
{
	if (Source < Destin) {
		Source += Length;
		Destin += Length;
		while (Length-- > 0) {
			*--Destin = *--Source;
		}
	} else {
		while (Length-- > 0) {
			*Destin++ = *Source++;
		}
	}
}

#if DEBUGGING
ULONG Zcp2ul(cp)		/* convert charptr to ULONG */
voidptr cp;
{
	return (ULONG)(cp);
}
#endif

/*****************************************************************************

	ZClnUp()

	This function cleans up in preparation for terminating TECO-C.

*****************************************************************************/

VVOID ZClnUp (VVOID)			/* cleanup for TECO-C abort */
{
	DBGFEN(3,"ZClnUp","closing terminal channels and exiting");
	if (tty_set == TRUE) {
		struct MsgPort *conid;           /* for process id     */
		long arg[NARGS];                /* array of arguments */
		struct Process *myprocess;     /* me!                */

		myprocess = (struct Process *) FindTask(NULL);     

/*
 * get console handler
 */
		conid = (struct MsgPort *) myprocess->pr_ConsoleTask;

		arg[0]=FALSE; /* turn RAW mode off */
/*
 * No point in checking result here - we're going to exit anyway.
 */
		sendpkt(conid,ACTION_SCREEN_MODE,arg,NARGS);
#if DEBUGGING
		printf("CON: mode set...\n");
#endif
	}		
}

/*****************************************************************************

	ZDoCmd()

	This function terminates TECO and feeds a command line to the
command line interpreter.  The command to be executed is passed to this
function in the file name buffer (FBf).


*****************************************************************************/

VVOID ZDoCmd()			/* die and pass command to OS */
{
	char	buf[128+1];
	char	*space_p;

	DBGFEN(1,"ZDoCmd",NULL);

/*
 * 1. Terminate buf[] and command line in FBf
 * 2. make local copy since FBf will be free'd in ZClnUp()
 * 3. separate program name from arguments, if any
 * 4. Call ZClnUp to free up everything
 * 5. Execute the command line, with optional arguments.  If we know where
 *    the command processor is, use it so we can execute .BAT batch files
 * 6. we shouldn't be here, exit
 */

	buf[128] = *FBfPtr = '\0';
	(void)strncpy(buf, FBfBeg, 128);
	if ((space_p = strchr(buf,' ')) != NULL) {
		*space_p++ = '\0';
	}
	ZClnUp();

/* 
 * We should unload teco before calling system() - this will require some
 * low-level dorking about with AmigaDOS which I don't want to do right now.
 */
	if (*buf) {
		system(buf);
	}
	TAbort(EXIT_SUCCESS);

/* 
 * We should never reach this statement.
 */
	(void)perror ("");
	TAbort (EXIT_FAILURE);
}

/*****************************************************************************

	ZDspBf()

	This function displays a buffer of a given length on the terminal
screen.  On the VAX (and maybe other systems) doing any kind of output
involves a fair amount of overhead,  regardless of the size of the buffer
being output.  It is therefore better to make a single call to the operating
system's output function than to call the function for each and every
character.  If such improvements do not apply to the system this program
is running on,  then this function can simply call ZDspCh for every character
in the buffer.

*****************************************************************************/

VVOID ZDspBf(buffer, length)	/* output a buffer to terminal */
charptr buffer;
SIZE_T length;
{
	if (write(fileno(stdout), buffer, length) == -1) {
		/* Like this will work!??? */
		puts("Unable to write to terminal in function ZDspBf");
		TAbort(EXIT_FAILURE);
	}
}

/*****************************************************************************

	ZDspCh()

	This function outputs a single character to the terminal.

*****************************************************************************/

VVOID ZDspCh(Charac)		/* output a character to terminal */
char Charac;
{
	if (write(fileno(stdout), &Charac, 1) == -1) {
		puts("Unable to write to terminal in function ZDspCh");
		TAbort(EXIT_FAILURE);
	}
}

/*****************************************************************************

	ZExCtB()

	This function implements the TECO ^B command,  which returns the
current date encoded in the following way:

		((year-1900)*16+month)*32+day

*****************************************************************************/

DEFAULT ZExCtB()			/* return current date */
{
	time_t clock;
	struct tm *time_of_day;
	int tecodate;

	DBGFEN(1,"ZExCtB","");
	clock=time(NULL);
	time_of_day=localtime(&clock);

	tecodate = ((time_of_day->tm_year)*16+time_of_day->tm_mon+1)*32
		+ time_of_day->tm_mday ;

	DBGFEX(1,DbgFNm,"PushEx()");
	return PushEx(tecodate, OPERAND);
}

/*****************************************************************************

	ZExCtH()

	This function implements the TECO ^H command,  which returns the
current time encoded in the following way:

		(seconds since midnight) / 2

*****************************************************************************/

DEFAULT ZExCtH()			/* return current time */
{
	time_t clock;
	struct tm *time_of_day;
	int tecotime;

	DBGFEN(1,"ZExCtH","");
	clock=time(NULL);
	time_of_day=localtime(&clock);

	tecotime = time_of_day->tm_hour * 60	/* hours * 60 */;
	tecotime += time_of_day->tm_min;	/* minutes */
	tecotime *= 30;
	tecotime += time_of_day->tm_sec >> 1;	/* seconds / 2 */

	DBGFEX(1,DbgFNm,"PushEx()");
	return PushEx(tecotime, OPERAND);
}

/*****************************************************************************

	ZExeEJ()

	This function executes an EJ command,  which returns environment
characteristics.  It returns:

	-1EJ	1024 under VAX/VMS	(4*256 = VAX, 0 = VMS in native mode)
		1025 under Ultrix	(4*256 = VAX, 1 = Ultrix)
		25600 under Sun/SunOS	(100*256 = Sun, 0 = SunOS)
		25856 under MS-DOS	(101*256 = IBM-PC, 0 = MS-DOS)
		26112 under AmigaDOS 1.3 (102*256 = Amiga, 0 = AmigaDOS 1.3)

	0EJ	process id on VAXen, 0 on anything else

	1EJ	0 on all systems

	2EJ	UIC, in longword format (unlike TECO-11) on VAX/VMS,
		0 on all other systems.

*****************************************************************************/

DEFAULT ZExeEJ()			/* execute an EJ command */
{
	DBGFEN(1,"ZExeEJ",NULL);
	if (EStTop == EStBot) {			/* if no numeric argument */
		NArgmt = 0;			/* default is 0EJ */
	} else {
		UMinus();			/* if -EJ, make it -1EJ */
		if (GetNmA() == FAILURE) {	/* get numeric argument */
			DBGFEX(1,DbgFNm,"FAILURE");
			return FAILURE;
		}
	}

	if (NArgmt == -1) {
		DBGFEX(1,DbgFNm,"PushEx(26112)");
		return PushEx((LONG)26112, OPERAND);	/* means "Amiga" */
	}

	if (NArgmt == 0) {
		DBGFEX(1,DbgFNm,"PushEx(0)");
		return PushEx((LONG)0, OPERAND);
	}

	DBGFEX(1,DbgFNm,"ExeNYI()");
	return ExeNYI();
}

/*****************************************************************************

	ZExit()

	This function terminates TECO-C with a status value.

*****************************************************************************/

VVOID ZExit(estat)		/* terminate TECO-C */
DEFAULT estat;
{
	exit(estat);
}

/*****************************************************************************

	ZFree()

	This function frees memory previously allocated by the ZAlloc
function.

*****************************************************************************/

VVOID ZFree(pointer)		/* free memory allocated by ZAlloc */
voidptr pointer;
{
	free(pointer);
}

/*****************************************************************************

	ZHelp()

	This function accepts a help string and displays the corresponding
help text.

	it should be control-C interrupt-able.

*****************************************************************************/

VVOID ZHelp(HlpBeg, HlpEnd, SysLib, Prompt)
charptr HlpBeg;		/* first char of help request */
charptr HlpEnd;		/* last character of help request */
BOOLEAN SysLib;		/* use default HELP library? */
BOOLEAN	Prompt;		/* enter interactive help mode? */
{
	(void)ExeNYI();
}

/*****************************************************************************

	ZIClos()

	This function closes the current input file.  It must

	1.  if current input stream is not open,  simply return
	2.  close the input file
	3.  set open indicator to FALSE

*****************************************************************************/

VVOID ZIClos(IfIndx)			/* close input file */
DEFAULT	IfIndx;				/* index into IFiles array */
{
	DBGFEN(2,"ZIClos",NULL);

	if (IsOpnI[IfIndx]) {			/* if it's open */
		if (fclose(IFiles[IfIndx]) == EOF) {
			ZErMsg();
			ErrMsg(ERR_UCI);
			ZExit(EXIT_FAILURE);
		}
		IsOpnI[IfIndx] = FALSE;
	}
	DBGFEX(2,DbgFNm,NULL);
}

/*****************************************************************************

	ZOClDe()

	This function closes and deletes the current output stream.  It must

	1.  if no current output stream is defined,  simply return
	2.  close the output stream
	3.  delete the file just closed

*****************************************************************************/

VVOID ZOClDe(OfIndx)			/* close and delete output file */
DEFAULT	OfIndx;				/* index into OFiles array */
{
	DBGFEN(2,"ZOClDe",NULL);

	if (IsOpnO[OfIndx]) {		/* if output stream is open */
		if (fclose(OFiles[OfIndx].OStrem) == EOF) {
			ZErMsg();
			ErrStr(ERR_UCO, OFiles[OfIndx].OFNam);
			exit(EXIT_FAILURE);
		}
		if (remove(OFiles[OfIndx].OFNam) != 0) {
			ZErMsg();
			ErrStr(ERR_UCD, OFiles[OfIndx].OFNam);
			exit(EXIT_FAILURE);
		}
		IsOpnO[OfIndx] = FALSE;
	}
	DBGFEX(2,DbgFNm,NULL);
}

/*****************************************************************************

	ZOClos()

	This function closes the current output stream.  It is only called
when an output stream is defined.  It must

	1.  flush output to the stream,  if neccessary
	2.  close the stream
	3.  set OFile to -1

*****************************************************************************/

VVOID ZOClos(OfIndx)		/* close output file */
DEFAULT	OfIndx;			/* index into OFiles array */
{
	char	*DotPtr;
	char	TmpFsp[FILENAME_MAX];

	DBGFEN(2,"ZOClos",NULL);

	if (!IsOpnO[OfIndx])				/* if it's not open */
		return;					/* we're done */

	if (fclose(OFiles[OfIndx].OStrem) == EOF) {	/* close it */
		ZErMsg();
		ErrMsg(ERR_UCO);			/* unable to close */
		exit(EXIT_FAILURE);
	}

	if (OFiles[OfIndx].OTNam[0] != '\0') {	/* if temporary output file */
		(void)strcpy(TmpFsp, OFiles[OfIndx].OTNam);/* copy to TmpFsp */
		DotPtr = strchr(TmpFsp, '.');		/* find the "." */
		if (DotPtr != NULL) {			/* if "." exists */
			*DotPtr = '\0';			/* make it null */
		}
		(void)strcat(TmpFsp, ".bak");		/* append ".bak" */
		if (access(TmpFsp, 0) == 0) {		/* old "x.bak"? */
#if DEBUGGING
			sprintf(DbgSBf,
				"deleting old version of %s\r\n",
				TmpFsp);
			DbgFMs(2,DbgFNm,DbgSBf);
#endif
			if (remove(TmpFsp) != 0) {	/* delete it */
				ZErMsg();
				ErrMsg(ERR_UCO);
				exit(EXIT_FAILURE);
			}
		}
#if DEBUGGING
		sprintf(DbgSBf,"renaming %s to %s\r\n",
			OFiles[OfIndx].OTNam, TmpFsp);
		DbgFMs(2,DbgFNm,DbgSBf);
#endif
		if (rename(OFiles[OfIndx].OTNam,TmpFsp)) {
			ZErMsg();
			ErrMsg(ERR_UCO);
			exit(EXIT_FAILURE);
		}
#if DEBUGGING
		sprintf(DbgSBf,"renaming %s to %s\r\n",
			OFiles[OfIndx].OFNam, OFiles[OfIndx].OTNam);
		DbgFMs(2,DbgFNm,DbgSBf);
#endif
		if (rename(OFiles[OfIndx].OFNam,OFiles[OfIndx].OTNam)) {
			ZErMsg();
			ErrMsg(ERR_UCO);
			exit(EXIT_FAILURE);
		}
	}

	IsOpnO[OfIndx] = FALSE;
	DBGFEX(2,DbgFNm,NULL);
}

/*****************************************************************************

	ZOpInp()

	This function opens an input file.  The name of the file is pointed
to by FBfBeg.  FBfPtr points to the character following the last character of
the file name.  This function opens files in response to the EB, ER and EQ
commands.  It does not open input files for the EI command.

*****************************************************************************/

DEFAULT ZOpInp(IfIndx, EIFile, RepFNF)
DEFAULT IfIndx;			/* index into file data block array IFiles */
BOOLEAN	EIFile;			/* is it a macro file? (hunt for it) */
BOOLEAN RepFNF;			/* report "file not found" error? */

{
#if DEBUGGING
	static char *DbgFNm = "ZOpInp";
	sprintf(DbgSBf,", FBf = \"%.*s\"", (int)(FBfPtr-FBfBeg), FBfBeg);
	DbgFEn(2,DbgFNm,DbgSBf);
#endif

	*FBfPtr = '\0';			/* terminate the file name */

/*
 * If we're executing an EI command,  then the default file type (the part
 * of the filename following the ".") is "tec".  If we're supposed to use
 * the default type,  and if the filename doesn't already have a type,
 * then append ".tec" to the filename.
 */
	if (EIFile) {
		if (strchr(FBfBeg,'.') == NULL) {
			(void)strcat(FBfBeg,".tec");
			FBfPtr += 4;
		}
	}

	if ((IFiles[IfIndx] = fopen(FBfBeg, "r")) == NULL) {
		if (!RepFNF && ((errno == ENODEV) || (errno == ENOENT))) {
			DBGFEX(2,DbgFNm,"FILENF");
			return FILENF;
		}
		ZErMsg();
		DBGFEX(2,DbgFNm,"FAILURE");
		return FAILURE;
	}
	DBGFEX(1,DbgFNm,"SUCCESS");
	return SUCCESS;
}

/*****************************************************************************

	ZOpOut()

	This function creates (and opens) an output file.  The name of
the file to be created is pointed to by FBfBeg.  FBfPtr points to the
character following the last character of the file name.

*****************************************************************************/

/*
 * Unix file names do not have version numbers,  so we have to deal with
 * creating ".bak" versions of files.  For output files,  this means that
 * when the output file is opened,  we check if a file with the same name
 * already exists.  If a file already exists,  then we open a temporary
 * output file and,  when the file is closed,  the close routine will deal
 * with renaming things to make them come out right.  If no file with the
 * same name already exists,  then the output file can simply be opened.
 * The close routine will only rename files if a temporary file was created
 * by this routine.
 */

DEFAULT ZOpOut(OfIndx, RepErr)		/* open output file */
DEFAULT	OfIndx;				/* output file indicator */
BOOLEAN RepErr;				/* report errors? */
{
	char *tfname;

#if DEBUGGING
	static char *DbgFNm = "ZOpOut";
	sprintf(DbgSBf,", FBf = \"%.*s\"",(int)(FBfPtr-FBfBeg),FBfBeg);
	DbgFEn(2,DbgFNm,DbgSBf);
#endif

/*
 * If the output file already exists,  make a temporary file.  Note:  the
 * tempnam function is used instead of the ANSI C tmpnam function because
 * tmpnam will make a file name in /usr/tmp,  which we may not be able to
 * rename later if /usr/tmp is across disk partitions.
 * (That's for UNIX) AmigaDOS is similar. The tempdir, 't:' isn't going to
 * have files in it that are 'renamable' to the current directory.
 */
	*FBfPtr = '\0';
	if (access(FBfBeg, 0) == 0) {		/* if file already exists */
		tfname = (char *)tempnam("","tecoc"); /* null = current dir */
		(void)strcpy(OFiles[OfIndx].OFNam, tfname);
		free(tfname);
		(void)strcpy(OFiles[OfIndx].OTNam, FBfBeg);
#if DEBUGGING
			sprintf(DbgSBf,
				"file %s already exists\r\n",
				FBfBeg);
			DbgFMs(2,DbgFNm,DbgSBf);
#endif
	} else {
		(void)strcpy(OFiles[OfIndx].OFNam, FBfBeg);
		OFiles[OfIndx].OTNam[0] = '\0';
	}

#if DEBUGGING
			sprintf(DbgSBf,
				"creating file %s\r\n",
				OFiles[OfIndx].OFNam);
			DbgFMs(2,DbgFNm,DbgSBf);
#endif
	OFiles[OfIndx].OStrem = fopen(OFiles[OfIndx].OFNam, "w");
	if (OFiles[OfIndx].OStrem == NULL) {
		if (RepErr)
			ZErMsg();
		DBGFEX(2,DbgFNm,"FAILURE");
		return FAILURE;
	}

	DBGFEX(2,DbgFNm,"SUCCESS");
	return SUCCESS;
}

/*****************************************************************************

	ZPrsCL()

	Parse the command line using a TECO macro.

	load q-register Z with the command line
	if USE_ANSI_CLPARS
		directly execute command-line parsing macro in clpars[]
	else
		load q-register Y with a command-line parsing macro
		do an MY$$

*****************************************************************************/

VVOID ZPrsCL(argc, argv)		/* parse a TECOC command line */
int argc;
char *argv[];
{
	int i;
	char TmpBuf[256];
	SIZE_T	line_len;

	DBGFEN(2,"ZPrsCL",NULL);

/*
  If the command line contains arguments,  construct a replica of the command
  line in Q-register Z.  It's a "replica" because spacing might be wrong.
*/
	if (argc > 1) {
		TmpBuf[0] = '\0';
		for (i=1; i<argc; i++) {
			(void)strcat(TmpBuf, *++argv);
			(void)strcat(TmpBuf, " ");
		}
		line_len = strlen(TmpBuf)-1;	/* remove trailing space */
		QR = &QRgstr[35];		/* 35 = q-register Z */
		if (MakRom(line_len) == FAILURE) {
			DBGFEX(2,DbgFNm,"exiting with EXIT_FAILURE");
			exit(EXIT_FAILURE);
		}
		MEMMOVE(QR->Start, TmpBuf, line_len);
		QR->End_P1 += line_len;		/* length of q-reg text */
	}

#if USE_ANSI_CLPARS

/*
 * execute imbedded command line-parsing macro directly from clpars[]
 */

	CStBeg = CBfPtr = clpars;		/* command string start */
	CStEnd = clpars + CLPARS_LEN;		/* command string end */
	EStTop = EStBot;			/* clear expression stack */
	ExeCSt();				/* execute command string */

#else

/*
 * Load imbedded command-line parsing macro into Q-register Y
 */

	QR = &QRgstr[34];			/* 34 = q-register Y */
	if (MakRom((SIZE_T)CLPARS_LEN) == FAILURE) {
		DBGFEX(2,DbgFNm,"MakRom(CLPARS_LEN) failed, calling exit()");
		exit(EXIT_FAILURE);
	}
	for (i = 0; i < CLPARS_LINES; i++) {
		line_len = strlen(clpars[i]);
		MEMMOVE(QR->End_P1, clpars[i], line_len);
		QR->End_P1 += line_len;		/* length of q-reg text */
	}

/*
 * Execute an MY$$ command.
 */

	CBfPtr = (charptr)"my\33\33";		/* command string start */
	CStEnd = CBfPtr + 3;			/* command string end */
	EStTop = EStBot;			/* clear expression stack */
	ExeCSt();				/* execute command string */

/*
 * Clear the command-line parsing macro from Q-register Y
 */

	QR = &QRgstr[34];			/* 34 = q-register Y */
	ZFree (QR->Start);
	QR->Start = QR->End_P1 = NULL;

#endif

	DBGFEX(2,DbgFNm,NULL);
}

/*****************************************************************************

	ZPWild()

	This function presets the wildcard lookup filename.  It is
called when the user executes an ENfilename$ command.  Later executions of
the EN$ command will cause the ZSWild function to be called to return
successive wildcard matches.

*****************************************************************************/

DEFAULT ZPWild()		/* preset the wildcard lookup filename */
{
/* ??? use sh -c ? */
	return ExeNYI();
}

/*****************************************************************************

	ZRaloc()

	This function performs the standard C library function realloc.

*****************************************************************************/

voidptr ZRaloc(OldBlk, NewSiz)		/* re-allocate memory */
voidptr OldBlk;
SIZE_T NewSiz;
{
	return (voidptr *)realloc(OldBlk, NewSiz);
}

/*****************************************************************************

        ZRdLin()

        This function reads a line from a file.  It is passed a buffer, the
size of the buffer, and a file pointer.  It returns the length of the line,
or sets IsEofI[] to TRUE if the end of file is encountered.

*****************************************************************************/

DEFAULT ZRdLin(ibuf, ibuflen, IfIndx, retlen)
charptr ibuf;
ptrdiff_t ibuflen;
int IfIndx;
DEFAULT *retlen;
{
        DBGFEN(3,"ZRdLin",NULL);
/*
 * make sure ibuflen is < 32K since fgets() requires an int
 */

        if (ibuflen > (ptrdiff_t) 32767) {
                ibuflen = (ptrdiff_t) 32767;
        }

/*
 * guarantee a trailing NUL in the buffer so strlen() always works
 */

        --ibuflen;
        ibuf[ibuflen] = '\0';

        if (fgets((char *)ibuf, (int)ibuflen, IFiles[IfIndx]) == NULL) {
                if (ferror (IFiles[IfIndx])) {
			ZErMsg();
                        DBGFEX(3,DbgFNm,"FAILURE, ferror() after fgets()");
                        return (FAILURE);
                }
		IsEofI[IfIndx] = TRUE;
                DBGFEX(3,DbgFNm,"SUCCESS, eof after fgets()");
        } else {
                *retlen = strlen((char *)ibuf);
		if ((EzFlag & EZ_UNIXNL) == 0) {       /* cnvrt \n to \r\n? */
		    if (ibuf[(*retlen)-1] == '\n') {
			ibuf[(*retlen)-1] = CRETRN;
			ibuf[*retlen] = LINEFD;
			*retlen += 1;
		    }
		}
#if DEBUGGING
                sprintf(DbgSBf,"SUCCESS, retlen = %d", *retlen);
                DbgFEx(3,DbgFNm,DbgSBf);
#endif
        }
        return SUCCESS;
}

/*****************************************************************************

	ZScrOp()

	This function is called to perform special screen functions.
	This is not yet finished...

*****************************************************************************/

VVOID ZScrOp(OpCode)		/* do a screen operation */
int OpCode;			/* code for operation */

{
	if (CrType == UNTERM) {		/* if unknown terminal type */
		return;			/* can't do screen operations */
	}

	switch (OpCode) {
		case SCR_CUP:	fputs("\0x1b[1A",stdout); /* cursor up */
				break;
		case SCR_EEL:	fputs("\0x1b[1K",stdout); /* clear to end of line */
				break;
		case SCR_ROF:	fputs("\0x1b[31m",stdout); /* reset standout mode */
				break;
		case SCR_RON:	fputs("\0x1b[33m",stdout); /* standout mode on */
				break;
	}
}

/*****************************************************************************

	ZSetTT()

	This function sets or clears terminal parameters.  The only terminal
parameters that TECO can set are

	1. whether the terminal can display 8-bit characters
	2. the number of rows
	3. the number of columns

*****************************************************************************/

DEFAULT ZSetTT(TTWhat, TTVal)	/* tell operating system we set the term. */
DEFAULT TTWhat;		/* what terminal parameter to set */
DEFAULT TTVal;		/* what to set it to */
{
	return ExeNYI();
}

/*****************************************************************************

	ZSWild()

	This function searches for the next wildcard filename.  It
is called when the user executes an "EN$" or ":EN$" command.  If the user
executes an "ENfilename$" command,  the ZPWild function is called,  not this
function.

	This function returns

		1. SUCCESS if the filename buffer has a new file name
		2. FAILURE if the search failed somehow other than FILENF
		3. FILENF if no more occurrences of the wildcard exist

*****************************************************************************/

DEFAULT ZSWild()		/* search for next wildcard filename */
{
	return ExeNYI();
}

/*****************************************************************************

	ZTrmnl()

	This function sets up the input/output of commands.  Usually, that
means the input/output channels to the terminal,  but TECOC might be run
from a command procedure (under VMS) or a script file (under Unix),  and
that possibility must be handled.  In addition,  the handling of interrupts
is found here.
	In general,  this function must:

		1. Set TIChan so it can be used to read commands
		2. Set TOChan so it can be used for output
		3. handle interrupts
		4. initialize CrType (what kind of terminal it is)
		5. initialize EtFlag (terminal capability bits)
		6. initialize HtSize (number columns terminal has)
		7. initialize VtSize (number rows terminal has)

	When TECO is started, the terminal will probably be set up for
buffered I/O,  so characters won't be received until a RETURN is hit,  and
they will be automatically echoed.  Set the terminal up for raw I/O,  so each
character is received when it is struck, and no echoing is performed.  Save
the terminal characteristics so when we exit we can reset them (in ZClnUp)
to what they were before we changed them.

*****************************************************************************/

static VVOID CntrlC()
{
	if (EtFlag & ET_TRAP_CTRL_C) {		/* if user wants it */
		EtFlag &= ~ET_TRAP_CTRL_C;	/* turn off bit */
	} else {
		if (EtFlag & ET_MUNG_MODE)	/* if in MUNG mode */
			TAbort(EXIT_SUCCESS);
		GotCtC = TRUE;			/* set "stop soon" flag */
	}
	signal(SIGINT, CntrlC);
}

/*
 * ZTrmnl - set up terminal modes
 *
 */
VVOID ZTrmnl()			/* set up I/O to the terminal */
{

	EtFlag = ET_READ_LOWER |	/* guess: term has lowercase and */
		 ET_SCOPE;		/* it's a scope, not hardcopy */

/*
 * get terminal characteristics and set some signals
 */
	if (isatty(fileno(stdin))) tty_set = TRUE;	/* tell ZClnUp to clean up */
	signal(SIGINT, CntrlC);			/* call CntrlC on interrupt */

/*
 * set window to RAW: mode instead of CON: mode - this gets rid of echo.
 */
	if (tty_set) {
		struct MsgPort *conid;         /* for process id     */
		long   arg[NARGS],res1;        /* holds result       */ 
		struct Process *myprocess;     /* me!                */

		myprocess = (struct Process *) FindTask(NULL);     

/*
 * get console handler.
 */
		conid = (struct MsgPort *) myprocess->pr_ConsoleTask;

		arg[0]=DOSTRUE;			/* arg1=TRUE - set RAW mode */
		res1 = sendpkt(conid,ACTION_SCREEN_MODE,arg,NARGS);

/*
 * If it is not possible to talk to our console, something really strange
 * has happened to AmigaDOS. The system will probably GURU soon.
 * We best exit immediately.
 */ 
		if(!res1)
			TAbort(EXIT_FAILURE);
#if DEBUGGING
		puts("In RAW: mode.");
#endif
	}

	CrType = VT102;			/* Let's pretend we are a VT102 */
#if VIDEO
	VtSize = HtSize = 0;
#endif
}


/*****************************************************************************

	ZVrbos()

	This function displays the verbose form of an error message.

*****************************************************************************/

VVOID ZVrbos(ErrNum, ErMnem)
WORD ErrNum;
char *ErMnem;
{
	char **TmpPtr;
#include "vrbmsg.h"

	ZDspBf("\r\n",2);
	for (TmpPtr = &ParaTx[StartP[LstErr]]; *TmpPtr; ++TmpPtr) {
		ZDspBf((charptr)*TmpPtr, strlen(*TmpPtr));
		ZDspBf("\r\n",2);
	}
}


/*****************************************************************************

	ZWrBfr()

	This function writes a buffer to a file,  one line at a time.  It is
passed an output file index and pointers to the beginning and end of the
buffer to be output.

*****************************************************************************/

DEFAULT ZWrBfr(OfIndx, BfrBeg, BfrEnd)
DEFAULT OfIndx;			/* index into OFiles array */
charptr BfrBeg;			/* address of output buffer beginning */
charptr BfrEnd;			/* address of output buffer end */
{

#if DEBUGGING
	static char *DbgFNm = "ZWrBfr";
	sprintf(DbgSBf,"OfIndx = %d, BfrBeg = %ld, BfrEnd = %ld",
		OfIndx, Zcp2ul(BfrBeg), Zcp2ul(BfrEnd));
	DbgFEn(2,DbgFNm,DbgSBf);
#endif

/*
 * If we're doing Unix-style editing,  where lines are terminated with a
 * line feed (newline) instead of a carriage-return/line-feed pair,  then
 * we can just fwrite the buffer.  Otherwise,  we have to scan the buffer
 * and convert CR/LF pairs to just LF for output.
 */
	if (EzFlag & EZ_UNIXNL) {
            ptrdiff_t bufsiz = BfrEnd-BfrBeg+1;
	    if (fwrite(BfrBeg, sizeof(char), bufsiz,
		       OFiles[OfIndx].OStrem) != bufsiz) {
		ZErMsg();
		ErrMsg(ERR_UWL);
		DBGFEX(2,DbgFNm,"Zfwrite() failed");
		return FAILURE;
	    }
	} else {
	    charptr BfrPtr = BfrBeg;		/* output buffer pointer */
	    while (BfrPtr <= BfrEnd) {
		if (*BfrPtr == CRETRN) {
		    BfrPtr++;
		    if ((BfrPtr > BfrEnd) || (*BfrPtr != LINEFD)) {
			BfrPtr--;
		    }
		}
		if (fputc(*BfrPtr, OFiles[OfIndx].OStrem) == EOF) {
		    ZErMsg();
		    ErrMsg(ERR_UWL);
		    DBGFEX(2,DbgFNm,"FAILURE");
		    return FAILURE;
		}
		++BfrPtr;
	    }
	}

	DBGFEX(2,DbgFNm,"SUCCESS");
	return SUCCESS;
}
