/*
 * Process input text line (non-request line).
 */

#include	<stdio.h>
#ifndef	STRING_H
#define	STRING_H <string.h>
#endif
#include	STRING_H

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

#include	"troffcvt.h"



static void	ReadTextLine (void);
static void	CheckInputLineTrap (void);
static void	EscapedText (XChar ec);


/*
 * Structure for mapping simple escape sequences to their resulting
 * output string.
 *
 * A "simple" escape sequence is one where the \ is followed by
 * a single character that requires nothing after it, such as \$.
 * Sequences such as \n or \* that require a register or string
 * name are not simple sequences.
 */

typedef	struct EscMap	EscMap;

struct EscMap
{
	char	emChar;
	char	*emStr;
};


static EscMap	escMap[] =
{
	{ '\\',	"backslash" },
	{ '&',	"zerospace" },
	{ '^',	"twelfthspace" },
	{ '|',	"sixthspace" },
	{ '0',	"digitspace" },
	{ ' ',	"hardspace" },
	{ '-',	"minus" },
	{ '\'',	"acute" },
	{ '`',	"grave" },
	{ 'a',	"leader" },
	{ 't',	"tab" },
	{ '\0',	(char *) NULL }
};


/*
 * This has to take some care not to read past the end of the input line...
 *
 * Process a single text line.  After it has been processed and flushed,
 * determine whether to issue a break, whether to decrement counters
 * (centered lines, underlined lines, etc.) and whether a space will be
 * necessary in front of the next text line.  None of these things will
 * be necessary if continuation is in effect (\c found in the curent line)
 * since the next line should be considered logically part of this one.
 *
 * Actually, \c doesn't seem to have that effect for .ul/.cu or .it
 * input line counting, only for .ce - so that's what's done below.
 * I don't understand why, but that's what troff versions I've looked
 * at do...
 */

void
ProcessText (void)
{
	inContinuation = 0;
	ReadTextLine ();
	if (inContinuation)
	{
		SetUnderline (ulCount - 1, cUlCount - 1);
		CheckInputLineTrap ();
		return;
	}

	/* break if in nofill mode or currently centering lines */

	if (fillMode == nofill || centerCount > 0)
		Break ();

	/*
	 * Need a space at beginning of next text line if in fill
	 * mode and mode isn't being temporarily overridden by
	 * centering being in effect.
	 */

	if (fillMode == fill && centerCount == 0)
		needSpace = 1;

	/*
	 * Turn centering off if it was on and this was the last
	 * line to be centered.  Filling/adjustment setting in effect
	 * at time centering was turned on now go back into effect.
	 */

	SetCFA (centerCount - 1, fillMode, adjust, adjMode);
	SetUnderline (ulCount - 1, cUlCount - 1);

	CheckInputLineTrap ();
}


/*
 * Do the following things, besides dumping out text.
 * - If line begins with space, do a break.
 * - If the line is *completely* blank (empty or all spaces), it's
 * equivalent to .sp 1.
 * - If a space is needed between this line and the previous one,
 * put out a space before the first character from this line is
 * written.
 */

static void
ReadTextLine (void)
{
XChar	c;
int	sCount, lineIsBlank = 1;
SpChar	*sp;
char	buf[bufSiz];
XChar	xbuf[bufSiz];

	if (ChPeek () == ' ')	/* if line begins with space, do break */
		Break ();


	/*
	 * While reading, keep track of sequences of spaces.  If they
	 * occur at the end of the line, forget about them.  If the
	 * line is completely blank, it's equivalent to .sp 1.
	 */

	for (;;)
	{
		sCount = 0;
		while ((c = ChIn ()) == ' ')
			++sCount;
		if (Eol (c))
			break;
		while (sCount-- > 0)		/* dump spaces if any */
			TextChOut (' ');	/* occurred before c */

		lineIsBlank = 0;		/* known not to be blank now */

		if (needSpace)
		{
			TextChOut (' ');
			needSpace = 0;
		}

		/*
		 * Check character to see if it's one of several special 
		 * characters.  Check these characters before checking for
		 * \(xy special characters or \x escaped characters: these
		 * characters can themselves be special or escaped characters,
		 * but they must be given different treatment than special
		 * or escaped characters are usually given.
		 */
		if (c == fieldDelimChar)
		{
			SpecialTextOut ((fieldDelimCount % 2) == 0 ?
					"fieldbegin" : "fieldend");
			++fieldDelimCount;
		}
		else if (c == fieldPadChar)	/* recognize, but only if */
		{				/* currently inside field */
			if (fieldDelimCount % 2)
				SpecialTextOut ("fieldpad");
		}
		else if (c == optHyphenChar)
			SpecialTextOut ("opthyphen");
		else if (Special (c))
		{
			if ((sp = LookupSpCharByCode (c)) != (SpChar *) NULL)
			{
				if (sp->spValue[0] == '@')
					SpecialTextOut (sp->spValue + 1);
				else
					TextStrOut (sp->spValue);
			}
		}
		else if (Esc (c))
			EscapedText (c);
		else if (c == '`')
		{
			if (ChPeek () == '`')
			{
				(void) ChIn ();
				SpecialTextOut ("quotedblleft");
			}
			else
				SpecialTextOut ("quoteleft");
		}
		else if (c == '\'')
		{
			if (ChPeek () == '\'')
			{
				(void) ChIn ();
				SpecialTextOut ("quotedblright");
			}
			else
				SpecialTextOut ("quoteright");
		}
		else	/* it's just plain text */
			TextChOut (c);
	}

	if (lineIsBlank)	/* line was blank; treated like .sp 1 */
	{
		sprintf (buf, "%csp 1\n", ctrlChar);
		(void) XStrCpy (xbuf, StrToXStr (buf));
		PushAnonString (xbuf, 0);
	}
	else
		FlushOText ();
}


/*
 * If input line trap should be sprung, push it onto the
 * input stack and remove the trap.
 */

static void
CheckInputLineTrap (void)
{
	if (itMacro != (XChar *) NULL && --itCount <= 0)
	{
		(void) PushMacro (itMacro, 0, (XChar **) NULL);
		XStrFree (itMacro);
		itMacro = (XChar *) NULL;
	}
}


static void
EscapedText (XChar ec)
{
EscMap	*em;
XChar	*xp;
char	*cp;
Param	val;
int	delim, unit, c, repChar;
short	iLevel;
char	buf[bufSiz];

	/* convert escaped character to un-escaped form */
	ec = FromEsc (ec);

	/* see if this is a simple single-char escape */

	for (em = escMap; em->emChar != '\0'; em++)
	{
		if (em->emChar == ec)
		{
			SpecialTextOut (em->emStr);
			return;
		}
	}

	/*
	 * Anything not found in the escape map above requires extra stuff
	 * to follow the escaped character.  Exceptions are \n, \$, \*, which
	 * should all already have been recognized in ChIn() and had input
	 * switched* accordingly (thus making the escape sequence disappear).
	 * \w will have been recognized when not in copy mode, and
	 * ProcessText() isn't called in copy mode, so the \w should not be
	 * seen here.
	 */

	switch (ec)
	{
	default:
		TextChOut (ec);
		break;
	case 'n':
	case '$':
	case '*':
	case '}':
	case '(':
	case 'w':
	case '{':
		ETMMsg ("%sEscapedText: logic error, wayward <\\%c>",
							FileInfo (), ec);
		break;
	case '.':
		TextChOut ('.');
		break;
	case 'b':				/* \b'stuff' */
	case 'o':				/* \o'stuff' */
		delim = ChIn ();
		iLevel = ILevel ();
		if (Eol (delim))		/* malformed */
		{
			UnChIn (delim);
			break;
		}
		ControlOut (ec == 'b' ? "bracket-begin" : "overstrike-begin");
		while (1)
		{
			c = ChIn ();
			if (Eol (c))
			{
				UnChIn (c);
				break;
			}
			if (c == delim && iLevel == ILevel ())
				break;
			cp = CharToOutputStr (c);
			if (*cp == '@')
				SpecialTextOut (cp+1);
			else
				TextStrOut (cp);
		}
		ControlOut (ec == 'b' ? "bracket-end" : "overstrike-end");
		break;
	case 'c':
		/*
		 * \c is recognized anywhere in line. troff manual indicates
		 * what to do only when the \c is at the end of the line
		 * (sec. 4.2), but recognizing it anywhere matches the behavior
		 * I observe in the troff's to which I have access.
		 */
		inContinuation = 1;
		break;
	case 'd':
		sprintf (buf, "motion %ld v", Units (.5, 'm'));
		ControlOut (buf);
		break;
	case 'r':
		sprintf (buf, "motion %ld v", Units (-1, 'm'));
		ControlOut (buf);
		break;
	case 'u':
		sprintf (buf, "motion %ld v", Units (-.5, 'm'));
		ControlOut (buf);
		break;
	case 'e':
		TextChOut (escChar);	/* print current escape char */
		break;
	case 'f':
		if ((xp = ParseNameRef ()) != (XChar *) NULL)
		{
			SetFont (xp);
			XStrFree (xp);
		}
		break;
	case 'h':				/* \h'expr' */
	case 'v':				/* \v'expr' */
	case 'x':				/* \x'expr' */
		delim = ChIn ();
		iLevel = ILevel ();
		if (Eol (delim))	/* malformed */
		{
			UnChIn (delim);
			break;
		}
		unit = (ec == 'h' ? 'm' : 'v');
		if (!ParseNumber (unit, 0, (Param) 0, &val))
			val = 0;
		while (1)
		{
			c = ChIn ();
			if (Eol (c))
			{
				UnChIn (c);
				break;
			}
			if (c == delim && iLevel == ILevel ())
				break;
		}
		switch (ec)
		{
		case 'h':
		case 'v':
			sprintf (buf, "motion %ld %c", val, ec);
			break;
		case 'x':
			sprintf (buf, "extra-space %ld", val);
			break;
		}
		ControlOut (buf);
		break;
	case 'k':		/* gobble mark char, ignore sequence */
		if (!Eol (ChPeek ()))
			(void) ChIn ();
		break;
	case 'l':
	case 'L':
		/*
		 * Get delim, parse expr, parse possible \&c, get
		 * delim, and write \line.
		*/
		delim = ChIn ();
		iLevel = ILevel ();
		if (Eol (delim))	/* malformed */
		{
			UnChIn (delim);
			break;
		}
		unit = (ec == 'l' ? 'm' : 'v');
		repChar = '\0';
		if (!ParseNumber (unit, 0, (Param) 0, &val))
			val = 0;
		if (ChPeek () == Esc ('&'))
			(void) ChIn ();
		if (!Eol (c = ChPeek ()) && !(c == delim && iLevel == ILevel()))
			repChar = ChIn ();
		while (1)
		{
			c = ChIn ();
			if (Eol (c))
			{
				UnChIn (c);
				break;
			}
			if (c == delim && iLevel == ILevel ())
				break;
		}
		/* note that the repChar isn't written... */
		c = (ec == 'l' ? 'h' : 'v');
		if (repChar == '\0')
			sprintf (buf, "line %ld %c", val, c);
		else
			sprintf (buf, "line %ld %c %s", val, c,
						CharToOutputStr (repChar));
		ControlOut (buf);
		break;
	case 'p':
		ControlOut ("break-spread");
		break;
	case 's':
		/* 0, n, +/-n, or nothing */
		SetSize (ParseSizeRef ());
		break;
	case 'z':
		if (!Eol (ChPeek ()))
		{
			sprintf (buf, "zero-width %s",
					CharToOutputStr (ChIn ()));
			ControlOut (buf);
		}
		break;
	}
}


/*
 * Convert a character code to a printable output string.  Escaped
 * characters are converted to unescaped characters, if they're
 * not known in the escape map (which is possibly wrong in some cases).
 * Specials are converted to @-form.
 *
 * Returns a pointer to a static buffer.  Return value should be copied
 * if caller needs to hang onto it for a while.
 *
 * Fails for things like space, \e, quotes, some others.
 * Oh, well.
 */

char *
CharToOutputStr (XChar c)
{
SpChar	*sp;
EscMap	*em;
static char	buf[bufSiz];

	if (Esc (c))
	{
		c = FromEsc (c);
		/* see if this is a simple single-char escape */

		for (em = escMap; em->emChar != '\0'; em++)
		{
			if (em->emChar == c)
			{
				sprintf (buf, "@%s", em->emStr);
				break;
			}
		}
		if (em->emChar == '\0')	/* not in map */
		{
			buf[0] = c;
			buf[1] = '\0';
		}
	}
	else if (!Special (c))
	{
		buf[0] = c;
		buf[1] = '\0';
	}
	else if ((sp = LookupSpCharByCode (c)) != (SpChar *) NULL)
		(void) strcpy (buf, sp->spValue);
	else
		ETMPanic ("CharToOutputStr: logic error");
	return (buf);
}
