/*
 * - Need platform-independent way of getting current date.
 * (Why? doesn't this work on a Macintosh?)
 */

/*
 * troffcvt - troff-to-XXX converter
 *
 * Options:
 *
 * -rN		Set internal resolution to N
 * -a file	Read action file
 * -A		Don't read default action file
 * -dN		Turn on debugging flag N (N missing = all flags)
 * -l		Turn on "\line file number" output
 * -mXX		Read macro package file XX
 * -t troff	Act like troff
 * -t nroff	Act like nroff
 * -C		Turn on compatibility mode (long names not recognized)
 *
 * The default action file is read before any other action files named on
 * the command line.
 *
 * 02 Apr 92	Paul DuBois	dubois@primate.wisc.edu
 *
 * 02 Apr 92 V1.00.  Created.
 *
 * 16 Dec 96
 * - Added -C option to turn on compatibility mode.  This was added because
 * other code was changed to allow long name references using [xxx], and
 * -C provides a way to turn that off if needed.  The register .C is also
 * set now to indicate whether compatibility mode is on or off.
 * 28 Dec 96
 * - Change -troff and -nroff options to -t troff and -t nroff.  Also allow
 * -t groff now, which sets the .g register non-zero and triggers other
 * groff-like behaviors.
 * - Add -A option to suppress reading default action file.
 * 24 Jan 97
 * - Make acting like groff the standard behavior. -tgroff is no longer
 * necessary.
 * 17 Feb 97
 * - Add -l command line option.
 */

#include	<stdio.h>
#include	<ctype.h>
#include	<sys/types.h>		/* for size_t */
#include	<sys/time.h>
#ifndef	STRING_H
#define	STRING_H <string.h>
#endif
#include	STRING_H

#include	"portlib.h"
#include	"etm.h"
#include	"memmgr.h"

#include	"tcgen.h"
#include	"tcunix.h"

#include	"troffcvt.h"

#define	defActionFile	"actions"

#define maxFileCount	20		/* max number of -a and -m files */
#define actionFileType	1
#define	macroFileType	2


static void	AddSetupFile (char *name, short type);
static void	ReadSetupFiles (void);
static void	SetupDefaults (void);
static void	WriteDefaults (void);
static int	ReadMacroFile (char *file);
static char	*FindFileByPathList (char *file, char *pathEnvVar);


int	troff = 1;			/* 0 = nroff, !0 = troff */

Param	resolution = 432;		/* units/inch */

int	allowInput = 1;

/* input chars */
XChar	escChar = defEscChar;		/* escape char */
int	doEscapes = 1;			/* whether to process escape char */
int	inCharTest = 0;			/* whether processing .if c */

XChar	ctrlChar = defCtrlChar;		/* request control character */
XChar	nbCtrlChar = defNbCtrlChar;	/* non-break control character */
XChar	curCtrlChar;			/* ctrl char from current request */
XChar	fieldDelimChar = '\0';		/* field delim char (null = none) */
XChar	fieldPadChar = '\0';		/* field padding char */
int	fieldDelimCount = 0;		/* fdc's seen on current line */
XChar	optHyphenChar;		/* optional hyphen char */

/* output chars */
XChar	tabChar = '\0';			/* tab char (null = motion) */
XChar	leaderChar = '\0';		/* leader char (null = motion) */

int	needSpace = 0;
int	inContinuation = 0;

int	ifLevel = 0;
int	ifResult = 1;	/* set TRUE so any wayward .el will be skipped */

XChar	fontTab[maxFonts][maxFontNameLen];
XChar	curFont[maxFontNameLen];
XChar	prevFont[maxFontNameLen];

XChar	*itMacro = (XChar *) NULL;
Param	itCount = 0;
XChar	*endMacro = (XChar *) NULL;

Param	centerCount = 0;
Param	ulCount = 0;
Param	cUlCount = 0;

Param	fillMode = fill;
Param	adjMode = adjFull;
Param	adjust = 1;

Param	curSize;
Param	prevSize;
Param	curSpaceSize;

Param	curVSize;
Param	prevVSize;
Param	curLineSpacing;
Param	prevLineSpacing;

Param	curIndent;
Param	prevIndent;
Param	curTempIndent;
Param	curLineLen;
Param	prevLineLen;

Param	curPageLen;
Param	curPageNum;
Param	curOffset;
Param	prevOffset;

Param	curHyphenMode;

Param	pageNumChar;
Param	curTitleLen;
Param	prevTitleLen;

Param	curTabCount;
Param	tabPos[maxTabStops];
char	tabType[maxTabStops];

Param	compatMode = 0;

long	debug = 0;

short	writeLineInfo = 0;

short	dumpBadReq = 0;


static short	readDefActionFile = 1;
static char	*fileList[maxFileCount];
static short	fileType[maxFileCount];
static short	fileCount = 0;


int
main (argc, argv)
int	argc;
char	*argv[];
{
char	buf[bufSiz];
char	*p;
int	inc;

	ETMInit (NULL);

	UnixSetProgPath (argv[0]);
	TCRSetOpenLibFileProc (UnixOpenLibFile);
	SetOpenMacroFileProc (UnixOpenMacroFile);

	/* examine option arguments */

	--argc;
	++argv;
	while (argc > 0 && argv[0][0] == '-' && argv[0][1] != '\0')
	{
		inc = 1;
		if (strncmp (argv[0], "-d", (size_t) 2) == 0)
		{
			if (argv[0][2] != '\0')	
				debug |= (1 << StrToLong (&argv[0][2]));
			else
				debug = 0xffffffff;
		}
		else if (strcmp (argv[0], "-A") == 0)
		{
			/* don't read default action file */
			readDefActionFile = 0;
		}
		else if (strncmp (argv[0], "-a", (size_t) 2) == 0)
		{
			if (argv[0][2] != '\0')
				p = &argv[0][2];
			else if (argc > 1)
			{
				p = argv[1];
				inc = 2;
			}
			else
				ETMPanic ("missing filename after -a");
			AddSetupFile (p, actionFileType);
		}
		else if (strncmp (argv[0], "-m", (size_t) 2) == 0)
		{
			if (argv[0][2] == '\0')
				ETMPanic ("empty macro package specifier");
			AddSetupFile (&argv[0][2], macroFileType);
		}
		else if (strncmp (argv[0], "-r", 2) == 0)
		{
			if (argv[0][2] != '\0')
				resolution = StrToLong (&argv[0][2]);
			else if (argc > 1)
			{
				resolution = StrToLong (argv[1]);
				inc = 2;
			}
			else
				ETMPanic ("missing resolution after -r");
			if (resolution < 1)
				ETMPanic ("nonsensical resolution: %ld",
								resolution);
		}
		else if (strncmp (argv[0], "-t", 2) == 0)
		{
			if (argv[0][2] != '\0')
				p = &argv[0][2];
			else if (argc > 1)
			{
				p = argv[1];
				inc = 2;
			}
			else
				ETMPanic ("missing formatter type after -t");
			if (strcmp (p, "troff") == 0)
				troff = 1;
			else if (strcmp (p, "nroff") == 0)
				troff = 0;
			else
				ETMPanic ("unknown formatter type: %s", p);
		}
		else if (strcmp (argv[0], "-C") == 0)
			compatMode = 1;
		else if (strcmp (argv[0], "-l") == 0)
			writeLineInfo = 1;
		else
			ETMPanic ("Unknown option: %s", argv[0]);
		argc -= inc;
		argv += inc;
	}

	if (debug)
	{
		/* don't want output order mixed up by buffering */
		setbuf (stdout, (char *) NULL);
		setbuf (stderr, (char *) NULL);
	}

	InitTransliterate ();
	(void) AllowOutput (0);		/* turn off output while doing setup */
	SetupDefaults ();

	/* reset these here - its known that resolution won't change */
	/*(void) StrNewRegister (".H", resolution, (Param) 0, "1", 1);*/
	/*(void) StrNewRegister (".V", resolution, (Param) 0, "1", 1);*/
	/*(void) StrNewRegister (".w", Units (1, 'n'), (Param) 0, "1", 1);*/

	InitEnvironments ();

	/* read default action file unless -A was specified */

	if (readDefActionFile)
	{
		if (!ReadActionFile (defActionFile))
		{
			ETMPanic ("cannot read action file <%s>",
							defActionFile);
		}
		InitEnvironments ();
	}

	/* Read action and macro files specified on command line */

	ReadSetupFiles ();

	/* Turn output back on and force out initial state */

	(void) AllowOutput (1);
	WriteDefaults ();

	if (argc == 0)		/* stdin */
	{
		CommentOut ("processing input (stdin)");
		if (!PushFile ((char *) NULL))
			ETMPanic ("Cannot read stdin");
		ReadInput ();
	}
	else while (argc > 0)
	{
	char	buf[bufSiz];

		if (strcmp (argv[0], "-") == 0)
		{
			CommentOut ("processing input file (stdin)");
			if (!PushFile ((char *) NULL))
				ETMMsg ("Cannot read stdin");
			else
				ReadInput ();
		}
		else if (strncmp (argv[0], "-a", (size_t) 2) == 0)
		{
			if (argv[0][2] != '\0')
				p = &argv[0][2];
			else if (argc > 1)
			{
				p = argv[1];
				--argc;
				++argv;
			}
			else
				ETMPanic ("missing filename after -a");
			if (!ReadActionFile (p))
				ETMMsg ("cannot read action file <%s>", p);
		}
		else
		{
			sprintf (buf, "processing input file %s", argv[0]);
			CommentOut (buf);
			if (!PushFile (argv[0]))
				ETMMsg ("Cannot open: %s", argv[0]);
			else
				ReadInput ();
		}
		--argc;
		++argv;
	}

	/* do .em here if defined (no arguments) */

	if (endMacro != (XChar *) NULL)
	{
		(void) PushMacro (endMacro, 0, (XChar **) NULL);
		ReadInput ();
	}

	/* Force a break in case a paragraph was still being collected */

	Break ();

	ETMEnd ();
	exit (0);
	/*NOTREACHED*/
}


/*
 * Set up defaults.
 *
 * troff manual says 26/27i for initial default offset, but that
 * was because of a 1/27i paper margin.  Use 1i.
 *
 * Other values are set to those in the Ossanna manual.
 */

static void
SetupDefaults (void)
{
struct tm	*tm;
time_t	t;
XChar	buf[2];
int	i;

	/*
	 * General registers
	 */

	(void) time (&t);
	tm = localtime (&t);
	(void) StrNewRegister ("dw", (Param) tm->tm_wday+1, (Param) 0, "1", 0);
	(void) StrNewRegister ("dy", (Param) tm->tm_mday, (Param) 0, "1", 0);
	(void) StrNewRegister ("mo", (Param) tm->tm_mon+1, (Param) 0, "1", 0);
	(void) StrNewRegister ("yr", (Param) tm->tm_year, (Param) 0, "1", 0);

	(void) StrNewRegister ("%", (Param) 0, (Param) 0, "1", 0);
	(void) StrNewRegister (".b", (Param) 0, (Param) 0, "1", 0);
	(void) StrNewRegister ("c.", (Param) 0, (Param) 0, "1", 0);
	(void) StrNewRegister (".R", (Param) 10000, (Param) 0, "1", 0);

	/*
	 * Read-only registers.  Most are created with value zero,
	 * but are modified by SetXXX() functions in setget.c.
	 * Members of the first group below are actually supported,
	 * members of the second group are created just so that
	 * attempts to modify them can be noticed.
	 *
	 * Resolution-related registers (.H, .V, .w) are affected by the
	 * -rN command-line option if that was given; but SetupDefaults()
	 * is not called until after options are examined, so it's safe
	 * to set them here and leave them alone.
	 */

	(void) StrNewRegister ("$$", (Param) GetPid (), (Param) 0, "1", 1);
	(void) StrNewRegister (".$", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".H", resolution, (Param) 0, "1", 1);
	(void) StrNewRegister (".V", resolution, (Param) 0, "1", 1);
	(void) StrNewRegister (".c", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".f", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".i", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".j", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".l", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".L", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".o", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".p", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".s", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".u", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".v", (Param) 0, (Param) 0, "1", 1);

	(void) StrNewRegister (".A", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".T", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".a", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".d", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".h", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".k", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".n", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".P", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".t", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".w", Units (1, 'n'), (Param) 0, "1", 1);
	(void) StrNewRegister (".x", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".y", (Param) 0, (Param) 0, "1", 1);
	(void) StrNewRegister (".z", (Param) 0, (Param) 0, "1", 1);

	/*
	 * groff-related stuff
	 *
	 * .g is non-zero if troffcvt is supposed to try to act like groff
	 * (which it always does).
	 * .C indicates whether or not compatibility mode is turned on.  If
	 * so, it turns off groff long names.  .g is always on.  .C is off,
	 * unless the -C command-line option is given.
	 * .ev is really a string-valued register, so the name here is
	 * fake.
	 */
	(void) StrNewRegister (".g", (Param) 1, (Param) 0, "1", 1);
	(void) StrNewRegister (".C", (Param) compatMode, (Param) 0, "1", 1);
	(void) StrNewRegister (".ev", (Param) 0, (Param) 0, "1", 1);

	/* font position zero is reserved */

	curFont[0] = '\0';
	prevFont[0] = '\0';
	buf[0] = ' ';
	buf[1] = '\0';
	SetFontNameBySlot ((Param) 0, buf);
	buf[0] = 'R';
	SetFontNameBySlot ((Param) 1, buf);
	buf[0] = 'I';
	SetFontNameBySlot ((Param) 2, buf);
	buf[0] = 'B';
	SetFontNameBySlot ((Param) 3, buf);
	buf[0] = 'S';
	for (i = 4; i < maxFonts; i++)
		SetFontNameBySlot ((Param) i, buf);

	buf[0] = 'R';
	SetFont (buf);	/* do twice to get current and previous */
	SetFont (buf);	/* both set to same value */

	SetCFA ((Param) 0, (Param) 1, (Param) 1, (Param) adjFull);
	SetUnderline ((Param) 0, (Param) 0);

	curPageLen = Units (11, 'i');
	curSize = prevSize = Units (10, 'x');
	curSpaceSize = Units (12./36., 'm');
	curVSize = prevVSize = Units (12, 'p');
	curLineSpacing = prevLineSpacing = Units (1, 'v');
	curIndent = prevIndent = Units (0, 'i');
	curTempIndent = Units (0, 'i');
	curLineLen = prevLineLen = Units (6.5, 'i');
	curOffset = prevOffset = Units (0, 'i');
	curTitleLen = prevTitleLen = Units (6.5, 'i');

	SetTabStops ((Param) 0, tabPos, tabType);

	/*
	 * Need curPageNum so can refer to "page-number" in
	 * parse-absrel-num action
	 */

	curPageNum = StrGetRegisterValue ("%");

	pageNumChar = '%';

	curHyphenMode = 1;
	optHyphenChar = ToEsc ('%');
}


/*
 * Since most of the SetXXX() functions only write out values
 * when they *change* from the current value (to avoid writing
 * unnecessary output), a trick is used to force out current
 * values:  change the relevant parameter externally to the
 * SetXXX() function, then pass the real value back in to
 * both set it and write it out.  Also then sync "previous" value
 * of revertibles to current value.
 *
 * Ugly tricks are also used to get the CFA, underlining and font
 * values written and synced.
 */

static void
WriteDefaults (void)
{
XChar	fontBuf[bufSiz];
XChar	fakeFontBuf[2];
char	buf[bufSiz];

	ControlOut ("setup-begin");

	sprintf (buf, "resolution %ld", resolution);
	ControlOut (buf);
	SetPageLength (++curPageLen - 1);

	SetSize (++curSize - 1);
	prevSize = curSize;
	SetSpaceSize (++curSpaceSize - 1);
	SetSpacing (++curVSize - 1);
	prevVSize = curVSize;
	SetLineSpacing (++curLineSpacing - 1);
	prevLineSpacing = curLineSpacing;
	SetOffset (++curOffset - 1);
	prevOffset = curOffset;
	SetLineLength (++curLineLen - 1);
	prevLineLen = curLineLen;
	SetIndent (++curIndent - 1);
	prevIndent = curIndent;
	SetTitleLength (++curTitleLen - 1);
	prevTitleLen = curTitleLen;
	SetHyphenation (++curHyphenMode - 1);

	/* more than a little ugly */

	(void) AllowOutput (0);
	SetCFA (centerCount, fillMode, (Param) !adjust, adjMode);
	(void) AllowOutput (1);
	SetCFA (centerCount, fillMode, (Param) !adjust, adjMode);

	(void) AllowOutput (0);
	SetUnderline (ulCount + 1, cUlCount);
	(void) AllowOutput (1);
	SetUnderline (ulCount - 1, cUlCount);

	(void) AllowOutput (0);
	(void) XStrCpy (fontBuf, curFont);
	fakeFontBuf[0] = ' ';	/* real fonts will never have this name */
	fakeFontBuf[1] = 0;
	SetFont (fakeFontBuf);
	(void) AllowOutput (1);
	SetFont (fontBuf);	/* will write out font */
	SetFont (fontBuf);	/* won't write, but syncs prevFont */

	SetPageNumber (++curPageNum - 1);

	ControlOut ("setup-end");

	SetTempIndent (++curTempIndent - 1);

	SetTabStops (++curTabCount - 1, tabPos, tabType);
}


/*
 * Add an action or macro file to the list of setup files to be read
 */

void
AddSetupFile (char *name, short type)
{
	if (fileCount >> maxFileCount)
		ETMPanic ("too many action/macro files named on command line");
	fileList[fileCount] = name;
	fileType[fileCount] = type;
	++fileCount;
}


/*
 * Read action and macro files named by -a and -m options on command line
 */

void
ReadSetupFiles (void)
{
char	buf[bufSiz];
char	*name;
short	i;
short	out;

	for (i = 0; i < fileCount; i++)
	{
		name = fileList[i];
/*
		ETMMsg ("Read %s file %s",
			fileType[i] == actionFileType ? "action" : "macro",
			fileList[i]);
*/
		if (fileType[i] == actionFileType)	/* action file */
		{
			if (!ReadActionFile (name))
				ETMMsg ("cannot read action file <%s>", name);
		}
		else					/* macro file */
		{
			sprintf (buf, "%s%s", MACROFILEPREFIX, name);
			if (!ReadMacroFile (buf))
				ETMPanic ("cannot read macro file for -m%s",
									name);
		}
	}
}


/*
 * Read and process input until there is no more
 */

void
ReadInput (void)
{
	while (ProcessLine ())
	{
		/* */
	}
}


/*
 * Process a single input line
 */

int
ProcessLine (void)
{
int	c;

	if ((c = ChPeek ()) == endOfInput)
		return (0);
	if (c == ctrlChar || c == nbCtrlChar)
		ProcessRequest (0);
	else if (c == ToEsc ('!'))	/* transparent mode */
	{
/* BUG */	/* this isn't really correct since */
/* BUG */	/* \n, \$, \*, \w should still be processed */
		SkipToEol ();
	}
	else
		ProcessText ();
	return (1);
}


int
ProcessActionList (Action *ap)
{
XChar	*argv[maxActionArgs+1];		/* +1 to allow for NULL at end */
int	status;
int	i;

	while (ap != (Action *) NULL)
	{
		if (Debug (bugActionProcess))
			ETMMsg ("processing %s %d", ap->actName, ap->actArgc);
		for (i = 0; i < ap->actArgc; i++)
			argv[i] = XStrAlloc (InterpretActionArg (ap->actArgv[i]));
		argv[i] = (XChar *) NULL;	/* add NULL argument at end */
		status = (*ap->actFunc) (ap->actArgc, argv);
		for (i = 0; i < ap->actArgc; i++)
			XStrFree (argv[i]);
		if (!status)
			return (0);	/* action failed, bail out */
		ap = ap->actNext;
	}
	return (1);
}


/*
 * Open and read a macro file.  Be careful not to pass NULL to PushFile(),
 * because it will try to read stdin then.
 *
 * Return non-zero if the file was read, zero otherwise
 */

static int
ReadMacroFile (char *file)
{
char	buf[bufSiz];
char	*p;
short	out;

	p = FindMacroFile (file);
	if (p == (char *) NULL)
		return (0);
	out = AllowOutput (1);
	sprintf (buf, "processing macro file %s", p);
	CommentOut (buf);
	(void) AllowOutput (0);
	if (!PushFile (p))
		return (0);
	ReadInput ();
	return (1);
}
