/**********************************************************************
* FLOW2TROFF:	Translate a "Flow" file into troff code.
*		Daniel J. Barrett, 1988.  barrett@cs.jhu.edu (ARPAnet).
*		PUBLIC DOMAIN.
*
* Usage:	Flow2Troff [options] Flow_file [Troff_File]
*
* Options:	See *usage_string[].
*
* Compiling:	(MANX Aztec C, 16-bit integers)
*		(You also need a version of "getopt()".)
*		cc Flow2Troff.c
*		ln Flow2Troff.o -lc
**********************************************************************/
	
#include <stdio.h>
#ifndef UNIX
#include <getopt.h>			/* I assume that UNIX/ULTRIX has   */
#endif					/* a built-in getopt() function.   */
	
#define FILE_END		-2	/* My own error flag.              */
#define NUM_HEADER_BYTES	42L	/* How many header bytes to skip.  */

#define EQUAL		!strcmp		/* 2 useful functions.             */
#define EXISTS(f)	!access(f,0)
	
#define UNDERLINE	1		/* The 5th byte of HSTL data has   */
#define	BOLD		2		/* the font information.  Lowest 3 */
#define ITALICS		4		/* bits are U, B, and I on/off.    */

#define	TRUE		1
#define	FALSE		0
#define	RIGHT		1
#define LEFT		0
	
long indent;				/* Number of times to indent.      */
int fontChange;				/* FALSE=plain, TRUE=bold/italics. */
int printing;				/* Used with dFlag.  TRUE if the   */
					/* current line indent is less     */
					/* dFlag.                          */
int quotes;
int underlined;

extern char *optarg;			/* Necessary getopt() variables.   */
extern int optind;
char optstring[] = "htp:v:i:d:";

int pFlag, vFlag, iFlag, hFlag, dFlag, tFlag;

/**********************************************************************
*			Usage information
**********************************************************************/

static char *usage_string[] = {
"",
"[33mFlow2Troff V1.0[0m by Daniel J. Barrett.  PUBLIC DOMAIN.",
"Convert from New Horizons Software \"Flow\" files to Troff files.",
"",
"Usage:		Flow2Troff [options] Flow_file [Troff_file]",
"",
"Options:	-p#	: Set troff point size (default 12)",
"		-v#	: Set troff vertical spacing (default 13)",
"		-i#	: Set indentation (default 3)",
"		-d#	: Print only to specified depth (default all)",
"		-h	: Permit hyphenation (default is none)",
"		-t	: Title the outline with \"Flow_file\"",
"",
"If not specified, Troff_file is written on standard output.",
"",
NULL };

	
/**********************************************************************
*			M A I N    P R O G R A M
**********************************************************************/

main(argc,argv)
int argc; char *argv[];
{
	FILE *infile=NULL, *outfile=NULL;	/* infile = FLOW file.  */
	char c;					/* outfile = TROFF file */

	/* Set default values for our flags. */
	pFlag=12, vFlag=13, iFlag=3, hFlag=FALSE, dFlag=0, tFlag=FALSE;

	/* Parse the command line, getting and setting all options. */
	while ((c = getopt(argc, argv, optstring)) != EOF)
		switch (c) {
			case 'p':	pFlag = atoi(optarg);
					CheckPositive(pFlag, 'p', ">");
					break;
			case 'v':	vFlag = atoi(optarg);
					CheckPositive(vFlag, 'v', ">");
					break;
			case 'i':	iFlag = atoi(optarg);
					CheckPositive(iFlag, 'i', ">");
					break;
			case 'd':	dFlag = atoi(optarg);
					CheckPositive(dFlag, 'd', ">=");
					break;
			case 'h':	hFlag = TRUE;
					break;
			case 't':	tFlag = TRUE;
					break;
			case '?':	Usage();
					break;
		}

	/* Open infile and outfile. */
	if (!OpenTheFiles(argc, argv, optind, &infile, &outfile))
		Cleanup(infile, outfile, "File opening failed... bye!");

	/* If infile is the wrong type, quit. */
	if (NotFlowFile(infile, outfile))
		Cleanup(infile, outfile, "Invalid input file... bye!");

	/* Skip the header bytes, convert to troff, and quit. */
	SkipBytes(infile, outfile, NUM_HEADER_BYTES);
	Flow2Troff(infile, outfile, argv[optind]);
	Cleanup(infile, outfile, NULL);
}

	
/**********************************************************************
*		Indentifying FLOW commands
**********************************************************************/

Flow2Troff(infile, outfile, title)
/* Continually read commands from the Flow file.  GetCommand() finds each
 * TEXT, NEST, or HSTL command and stores it in "command".  Then Convert()
 * processes that command and its data.  "title" is the name of the infile. */
FILE *infile, *outfile;
char *title;
{
	char command[5];
	int error;

	indent = 0;			/* Initialize global variables. */
	fontChange = FALSE;		/* No indent, plain font, and   */
	printing = TRUE;		/* printing turned on.          */
	underlined = FALSE;		/* Is current text underlined?  */
	quotes = LEFT;			/* Print left or right quotes?  */
	
	TroffHeader(outfile, title);	/* Output mandatory troff header. */

	do {
		if (GetCommand(infile, outfile, command) == FILE_END)
			return(FILE_END);
		error = Convert(infile, outfile, command);
	} while (!error);

	return(error);
}

	
GetCommand(infile, outfile, command)
/* Get the four-letter formatting command from infile. */
FILE *infile, *outfile;
char command[];
{
	int n=4;
	char c;

	StupidHack(infile, outfile);		/* Yeccchh. */

	/* Read a four-character command, one byte at a time. */
	while (n && ((c = getc(infile)) != EOF)) {
		command[4-n] = c;
		n--;
	}

	/* Did we get the whole command? */
	if (n)				/* We must have hit EOF...       */
		return(FILE_END);	/* ... so complain.              */
	else {
		command[4] = '\0';	/* Terminate command with a null */
		return(0);		/* so we can use it as a string. */
	}
}

	
Convert(infile, outfile, command)
/* Depending on what kind of command we have, run the appropriate 
 * Flow --> Troff conversion routine. */
FILE *infile, *outfile;
char *command;
{
	if (EQUAL(command, "TEXT"))		/* Actual text. */
		return(DumpText(infile, outfile));
	else if (EQUAL(command, "NEST"))	/* Indentation data. */
		return(Indent(infile, outfile));
	else if (EQUAL(command, "HSTL"))	/* Text style change. */
		return(ChangeStyle(infile, outfile));
	else {					/* Error! */
		fprintf(stderr, "Error found in file!\n");
		return(FILE_END);
	}
}

	
/**********************************************************************
*			The actual translation routines
**********************************************************************/

TroffHeader(outfile, title)
/* Output some mandatory Troff code into the outfile. */
FILE *outfile;
char *title;
{
	DefineUnderline(outfile);
	fprintf(outfile, ".ps %d\n", pFlag);	/* Point size.           */
	fprintf(outfile, ".vs %d\n", vFlag);	/* Vertical spacing.     */
	if (!hFlag)
		fprintf(outfile, ".nh\n");	/* No hyphenation.       */
	fprintf(outfile, ".ad b\n");		/* Left & right justify. */

	/* If we have a title, print it.  Else, just print 5 newlines. */
	if (tFlag)
		fprintf(outfile, ".sp 5\n.ft B\n.ce 1\n%s\n.ft R\n.sp 3\n",
			title);
	else
		fprintf(outfile, ".sp 5\n");
}

	
DefineUnderline(outfile)
/* Define a troff "underline string" command, ".us".  This is from the
 * NROFF/TROFF USER'S MANUAL, page 20,  in Volume 2 of THE UNIX PROGRAMMER'S
 * MANUAL. */
FILE *outfile;
{
	fprintf(outfile, ".de us\n\\\\$1\\l'|0\\(ul'\n..\n");
}

	
DumpText(infile, outfile)
/* For a TEXT command, find the length of its data, and then output that
 * data. */
FILE *infile, *outfile;
{
	int i;
	unsigned char len[4];
	long textLength=0L;
	char c;

	/* TEXT data is stored in a variable length field.  The first
	 * 4 bytes are a longword; they store the length of the text
	 * string immediately following. */

	/* Get length of text, in characters.  The length is stored as
	 * a 4-byte field.  We must convert this to a long. */

	for (i=0; i<4; i++)
		len[i] = getc(infile);

	textLength = (long)	 ((len[0] << 24)
				+ (len[1] << 16)
				+ (len[2] << 8 )
				+  len[3]);

	/* If we are printing (not indented past dFlag), print the text.
	 * If we were printing in an alternate font, return to plain.
	 * If we were not printing, just skip all the text data entirely. */

	if (printing) {
		for (i=0; i<textLength; i++) {
			c = getc(infile);
			if (underlined && c == '"') {
				if (quotes == LEFT) {
					fprintf(outfile, "``");
					quotes = RIGHT;
				}
				else {
					fprintf(outfile, "''");
					quotes = LEFT;
				}
			}
			else
				putc(c, outfile);
		}
		if (underlined) {		/* Terminate underlining. */
			putc('"', outfile);
			underlined = FALSE;
		}
		fprintf(outfile, "\n");
		if (fontChange) {
			fprintf(outfile, ".ft R\n");
			fontChange = FALSE;
		}
		fprintf(outfile, ".br\n");
	}
	else
		SkipBytes(infile, outfile, textLength);

	return(0);
}

	
Indent(infile, outfile)
/* Print the proper troff ".in" indenting information.  This algorithm
 * is not as straightforward as I thought it would be. */
FILE *infile, *outfile;
{
	long newIndent=0;		/* New indent value, to be read. */
	long Abs();			/* Absolute value.               */
	unsigned char temp[2];		/* Two bytes of newIndent value. */
	char plusMinus;			/* Holds either a '+' or a '-'.  */
	int i;

	/* NEST data is 6 bytes long.  The first four bytes are 0 0 0 2, and
	 * I don't know their meaning.  The last two bytes represent the
	 * absolute indentation from the left margin. */

	SkipBytes(infile, outfile, 4L);
	for (i=0; i<2; i++) {
		temp[i] = getc(infile);
		if (temp[i] == EOF)
		   Cleanup(infile, outfile, "Bad indentation data.. bye!");
	}
	newIndent = (long)(temp[1] + (temp[0] << 8));  /* New indent value. */

	/* INDENTATION ALGORITHM.
	 *
	 * Assume we are currently printing.
	 * 	If the -d flag is not specified, we simply do the indent.
	 *	Same deal if we DO have -d, but we're not indented past dFlag.
	 *	But if we used -d AND we indented too far, we turn off
	 *	 printing.
	 *
	 * Alternatively, assume we are NOT currently printing.  We could
	 *  get here ONLY if the -d flag has been set.
	 *	If the new indent value is greater than or equal to the -d
	 *	 value, then we do nothing... we still should not print.
	 *	Otherwise, the new indent value is less than the -d value.
	 *	 Turn printing back on.
	 *	 For all intents and purposes, we may now pretend that our
	 *	  current indent value was the maximum printable:  dFlag-1.
	 *	 If the newIndent value is ALSO dFlag-1, we do not need to
	 *	  do any indents... just stay where we are.  Otherwise, do
	 *	  an indent (which MUST be negative) backwards from dFlag.
	 *
	 * Simple, eh?							*/

	if (printing) {
		if (dFlag==0 || (newIndent < dFlag)) {
			plusMinus = ((newIndent - indent) >= 0)
				  ? '+'
				  : '-';
			fprintf(outfile, ".in %c%ld\n", plusMinus, 
				Abs((newIndent-indent)*iFlag));
		}
		else
			printing = FALSE;
	}
	else if (newIndent < dFlag) {
		printing = TRUE;
		if (newIndent != (dFlag-1))
			fprintf(outfile, ".in -%ld\n",
			 Abs((newIndent-(dFlag-1))*iFlag));
	}
	indent = newIndent;		/* Keep the new indent value. */
	return(0);
}

	
ChangeStyle(infile, outfile)
/* Change to bold, italics, underline. Troff cannot do both bold & italics
 * simultaneously, so bold takes precedence here.  Underlining is a real
 * hack. */
FILE *infile, *outfile;
{
	char style=0;

	/* HSTL data is 6 bytes.  The 5th byte contains style change info.
	 * The lowest bit is underline on/off, the next is bold on/off, and
	 * the third is italics on/off.  I don't know what the other 5 bytes
	 * stand for. */

	/* If we are printing, print the appropriate troff style change
	 * info.  Else, just skip the 6 bytes of HSTL data. */

	if (printing) {
		SkipBytes(infile, outfile, 4L);
		style = getc(infile);

		if (style & BOLD) {
			fprintf(outfile, ".ft B\n");
			fontChange = TRUE;
		}
		else if (style & ITALICS) {
			fprintf(outfile, ".ft I\n");
			fontChange = TRUE;
		}
		if (style & UNDERLINE) {
			underlined = TRUE;
			fprintf(outfile, ".us \"");   /* quote before text */
		}
		SkipBytes(infile, outfile, 1L);
	}
	else
		SkipBytes(infile, outfile, 6L);

	return(0);
}

/**********************************************************************
*			File opening routines
**********************************************************************/
	
OpenTheFiles(argc, argv, optind, infile, outfile)
/* Open input and output files, return their pointers in infile and
 * outfile.  If no outfile specified, use stdout. */
int argc;
char *argv[];
int optind;
FILE **infile, **outfile;
{
	int argsLeft = argc - optind;

	if (argsLeft == 2) {		/* infile & outfile were specified. */
		if ((*infile = fopen(argv[optind], "r")) == NULL) {
			perror(argv[optind]);
			return(FALSE);
		}
		optind++;
		if (DontOverwriteExistingFile(argv[optind]))
			return(FALSE);
		if ((*outfile = fopen(argv[optind], "w")) == NULL) {
			perror(argv[optind]);
			return(FALSE);
		}
	}
	else if (argsLeft == 1) {	/* Only infile specified. */
		if ((*infile = fopen(argv[optind], "r")) == NULL) {
			perror(argv[optind]); 
			return(FALSE);
		}
		*outfile = stdout;
	}
	else 					/* Bad syntax */
		Usage();
}

	
DontOverwriteExistingFile(filename)
/* If filename already exists, inform the user, who may choose to 
 * continue or quit. */
char *filename;
{
	static char *ex = "File \"%s\" already exists; overwrite it? (n/y): ";
	if (!EXISTS(filename))
		return(FALSE);
	else {
		fprintf(stderr, ex, filename);
		if (getchar() != 'y')
			return(TRUE);
		else
			return(FALSE);
	}
}

	
NotFlowFile(infile, outfile)
/* If file is not a FLOW file, return TRUE.  Otherwise, return FALSE.
 * We assume that infile points to the beginning of the file. */
FILE *infile, *outfile;
{
	int i;
	unsigned char buf[5];

	/* Check if the file is a custom IFF "FORM" file. */

	for (i=0; i<4; i++) {
		buf[i] = getc(infile);
		if (buf[i] == EOF)
			return(TRUE);
	}
	buf[4] = '\0';
	if (strcmp(buf, "FORM")) {
		fprintf(stderr, "Not an IFF FORM file.\n");
		return(TRUE);
	}

	/* Check if the type of the FORM file is "HEAD". */

	SkipBytes(infile, outfile, 4L);
	for (i=0; i<4; i++) {
		buf[i] = getc(infile);
		if (buf[i] == EOF)
			return(TRUE);
	}
	buf[4] = '\0';
	if (strcmp(buf, "HEAD")) {
		fprintf(stderr, "Infile is IFF FORM, but wrong type.\n");
		return(TRUE);
	}

	/* If we got here, then the file must be OK. */

	fseek(infile, 0L, 0);		/* Return to beginning of file. */
	return(FALSE);
}
	
/**********************************************************************
*			Miscellaneous little routines	
**********************************************************************/

SkipBytes(infile, outfile, n)
/* Skip over the next n bytes in file pointed to by infile.
 * If we reach EOF, quit. */
FILE *infile, *outfile;
long n;
{
	while (n && (getc(infile) != EOF))
		n--;
	if (n)
		Cleanup(infile, outfile, "File ended before I was done!");
}

	
Usage()
/* Print a program usage message, then exit. */
{
	char **str = usage_string;
	while (*str)
		fprintf(stderr, "%s\n", *(str++));
	exit(5);
}

	
Cleanup(infile, outfile, s)
/* Exit the program gracefully. */
FILE *infile, *outfile;
char *s;
{
	if (infile)
		fclose(infile);
	if (outfile)
		fclose(outfile);
	if (s)
		fprintf(stderr, "%s\n", s);
	exit(0);
}

	
StupidHack(infile, outfile)
/* Sometimes, there is a zero immediately following TEXT data.  I
 * have no idea why it is there.  Since it seems to contribute no
 * information useful for troff, I just skip it. */
FILE *infile, *outfile;
{
	char c;

	c = getc(infile);
	if (c == EOF)
		Cleanup(infile, outfile, NULL);
	else if (c != 0)
		ungetc(c, infile);
}

	
long Abs(x)
/* Return the absolute value of x. */
long x;
{
	return((x<0) ? -x : x);
}

	
CheckPositive(value, flag, sign)
/* Print an error message if the value of the flag is out of range. */
int value;
char flag, *sign;
{
	static char *message = "ERROR: -%c value must be %s 0.\n";

	if ((EQUAL(sign, ">") && (value <= 0))
	||  (EQUAL(sign, ">=") && (value < 0)))
		fprintf(stderr, message, flag, sign), exit(5);
}
