/*
 * Underline setting isn't really correct yet.
 *
 * Routines for setting various processing parameters and writing out
 * their values if they've changed.
 */

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

#include	"etm.h"

#include	"troffcvt.h"


#define	Max(a,b)	(a > b ? a : b)


static void SpitSetting (char *name, Param cur, Param new, Param lower);
static Param GetFontNumber (XChar *p);


void
SetPageLength (Param val)
{
char	buf[bufSiz];

	if (val != curPageLen)
	{
		sprintf (buf, "page-length %ld", val);
		ControlOut (buf);
		curPageLen = val;
		StrSetRegisterValue (".p", curPageLen);
	}
}


/*
 * Set centering, filling, adjusting stuff.  Test the various modes,
 * from lowest priority to highest, to figure out which control to
 * write out.  At the end, compare it with the one last written to
 * determine whether it really needs to be written.
 *
 * This has the property of setting all four state variables properly
 * and keeping them up to date internally, while writing only the
 * current setting explicitly so postprocessors don't have to mess
 * with interactions between them.
 */

void
SetCFA (Param nCenter, Param nFill, Param nAdjust, Param nAdjMode)
{
static char	*lastSetting = "";
char	*p = "";

	switch (adjMode = nAdjMode)
	{
	case adjLeft:	p = "adjust-left"; break;
	case adjRight:	p = "adjust-right"; break;
	case adjFull:	p = "adjust-full"; break;
	case adjCenter:	p = "adjust-center"; break;
	}

	if ((adjust = nAdjust) == 0)
		p = "adjust-left";
	if ((fillMode = nFill) == nofill)
		p = "nofill";
	if (nCenter < 0)	/* fix first if necessary */
		nCenter = 0;
	if ((centerCount = nCenter) > 0)
		p = "center";

	if (strcmp (p, lastSetting) != 0)	/* setting's changed */
		ControlOut (lastSetting = p);

	StrSetRegisterValue (".u", (Param) (fillMode == fill));
	StrSetRegisterValue (".j", (Param) (adjMode + (adjust ? 4 : 0)));
}


/*
 * Set the underlining properly, turning it on or off depending
 * on the current state and the new state.  If both are positive
 * regular ul takes precedence over continuous ul.
 *
 * Default is to assume it's off, and turn on if find out otherwise.
 */

void
SetUnderline (Param ul, Param cul)
{
static char	*lastSetting = "";
char	*p = "nounderline";

	if (ul < 0)
		ul = 0;
	if (cul < 0)
		cul = 0;

	if ((cUlCount = cul) > 0)		/* turn cul on */
		p = "cunderline";

	if ((ulCount = ul) > 0)			/* turn ul on */
		p = "underline";

	if (strcmp (p, lastSetting) != 0)	/* setting's changed */
		ControlOut (lastSetting = p);
}


/*
 * .ft 0 and \f0 are equivalent; they refer to last named but never
 * mounted font.
 *
 * It is necessary that new be a copy of the argument passed in if
 * the argument isn't a number, otherwise SetFont(prevFont) would
 * set curFont, but not cycle curFont into prevFont.
 */

void
SetFont (XChar *name)
{
XChar	new[maxFontNameLen], *p;
char	buf[bufSiz];
Param	number;

	if (name == (XChar *) NULL || XStrLen (name) >= maxFontNameLen)
		return;
	if (!AsciiStr (name))
	{
		ETMMsg ("%snon-ASCII font name",
					FileInfo (),
					XStrToStr (name));
		return;
	}
	new[0] = '\0';				/* empty font name */
	if (name[0] == 'P' && name[1] == '\0')	/* revert to previous */
		(void) XStrCpy (new, prevFont);
	else if ((p = GetFontName (name)) != (XChar *) NULL)
		(void) XStrCpy (new, p);

	if (new[0] == '\0')			/* didn't find it */
		return;

	if (XStrCmp (new, curFont) != 0)
	{
		sprintf (buf, "font %s", XStrToStr (new));
		ControlOut (buf);
	}

	(void) XStrCpy (prevFont, curFont);
	(void) XStrCpy (curFont, new);
	if ((number = GetFontNumber (curFont)) < 0)
	{
		number = 0;
		SetFontNameBySlot (number, curFont);
	}
	StrSetRegisterValue (".f", number);
}


/*
 * Given a font reference as a number or name, return the name
 * or NULL if the reference is bad.
 */

XChar *
GetFontName (XChar *p)
{
	if (p != (XChar *) NULL && Digit (*p))
	{
		if (*(p+1) != 0)	/* only one digit allowed */
			return ((XChar *) NULL);
		/* this will be NULL if bad */
		p = GetFontNameBySlot ((Param) (*p - '0'));
	}
	if (p == (XChar *) NULL || *p == 0)	/* null or empty? */
		return ((XChar *) NULL);
	return (p);
}


static Param
GetFontNumber (XChar *p)
{
Param	i;

	for (i = 0; i < maxFonts; i++)
	{
		if (fontTab[i] != (XChar *) NULL
			&& XStrCmp (fontTab[i], p) == 0)
				return (i);
	}
	return ((Param) -1);
}


/*
 * The zeroth font table entry is reserved.  troff uses position 0
 * for the hidden font (the last named, but unmounted font)
 */

XChar *
GetFontNameBySlot (Param pos)
{
	if (pos < 0 || pos >= maxFonts)
		return ((XChar *) NULL);
	return (fontTab[pos]);
}


void
SetFontNameBySlot (Param pos, XChar *name)
{
int	len;

	if (pos < 0 || pos >= maxFonts)
		return;
	if (name == (XChar *) NULL || (len = XStrLen (name)) == 0
			|| len >= maxFontNameLen)
		return;
	if (!AsciiStr (name))
	{
		ETMMsg ("%snon-ASCII font name <%s>",
				FileInfo (),
				XStrToStr (name));
		return;
	}
	(void) XStrCpy (fontTab[pos], name);
}


/*
 * If format isn't recognized, defaults are used.
 */

void
SetRegisterFormat (Register *rp, XChar *format)
{
int	fmt = numFmt, fmtWidth = 0;

	/*
	 * I suppose this should really check that ALL characters
	 * are numeric, but then what does it mean if they're not?
	 */
	if (Digit (*format))
	{
		if (XStrLen (format) > 1)	/* else leave at 0 */
			fmtWidth = XStrLen (format);
	}
	else switch (*format)
	{
	case 'i': fmt = lRomanFmt; break;
	case 'I': fmt = uRomanFmt; break;
	case 'a': fmt = lAlphaFmt; break;
	case 'A': fmt = uAlphaFmt; break;
	}
	rp->regFormat = fmt;
	rp->regFmtWidth = fmtWidth;
}


/*
 * Format a register value and return a pointer to the (static)
 * string result.  Registers with i, I, a or A format are number
 * formatted if the value is less than 1.
 *
 * Roman and Alphabetic are broken.  The former isn't even attempted,
 * and the algorithm for the latter doesn't always work.  I'm too
 * lazy to fix it right now.
 */

XChar *
FormatRegister (Register *rp)
{
static XChar	xbuf[bufSiz];
char	buf[bufSiz];
char	*p = buf;
Param	value = rp->regValue;
int	format = rp->regFormat;
int	wid = rp->regFmtWidth;
int	i, n;

	if (1 /* format == numFmt || value < 1 */ )
	{
		if (wid == 0)
			sprintf (buf, "%ld", value);
		else
			sprintf (buf, "%0*ld", wid, value);
	}
	else if (format == lAlphaFmt || format == uAlphaFmt)
	{
		n = 1;
		while (n <= value)
		{
			n *= 26;
			if (n < 0)	/* uh-oh...underflow */
				ETMPanic ("FormatRegister: numeric underflow");
		}
		while (n > 1)
		{
			n /= 26;
			i = 0;
			*p = 'a';
			while (value > n)
			{
				value -= n;
				(*p)++;
			}
			++p;
		}
		*p = '\0';
	}
	else	/* format is roman */
	{
		sprintf (buf, "%ld", value);	/* wimp out for now */
	}
	/* now uppercase the string if necessary */
	if (format == uAlphaFmt || format == uRomanFmt)
	{
		for (p = buf; *p != '\0'; p++)
		{
			if (islower (*p))
				*p = toupper (*p);
		}
	}
	(void) XStrCpy (xbuf, StrToXStr (buf));
	return (xbuf);
}


Param
GetRegisterValue (XChar *name)
{
Register	*rp;

	if ((rp = LookupRegister (name)) == (Register *) NULL)
		return ((Param) 0);
	return (rp->regValue);
}


/*
 * Perhaps this should auto-create registers?
 */

void
SetRegisterValue (XChar *name, Param val)
{
Register	*rp;

	if ((rp = LookupRegister (name)) == (Register *) NULL)
	{
		ETMMsg ("%sattempt to set non-existent register <%s>",
							FileInfo (),
							XStrToStr (name));
	}
	else
		rp->regValue = val;
}

/*
 * Versions of {Get,Set}RegisterValue() that can be used when you KNOW
 * you have a register name that can be expressed with ASCII characters.
 */

Param
StrGetRegisterValue (char *name)
{
Register	*rp;
XChar	xname[bufSiz];

	(void) XStrCpy (xname, StrToXStr (name));
	return (GetRegisterValue (xname));
}


void
StrSetRegisterValue (char *name, Param val)
{
Register	*rp;
XChar	xname[bufSiz];

	(void) XStrCpy (xname, StrToXStr (name));
	SetRegisterValue (xname, val);
}


/*
 * The following SetXXX() routines maintain the asked-for value
 * internally, to maintain the integrity of matching relative
 * changes (e.g., .xx -N followed by .xx +N), but won't write out
 * ridiculous values.  Instead they write out a lower-limit value
 * instead.
 *
 * Some parameters need to be clipped, however (e.g., indent, line
 * length).  For those, cur should be clipped before calling
 * SpitSetting().
 */

static void
SpitSetting (char *name, Param cur, Param new, Param lower)
{
char	buf[bufSiz];

	if (cur != new)
	{
		sprintf (buf, "%s %ld", name, Max (lower, new));
		ControlOut (buf);
	}
}


/*
 * Point size is maintained in points, not units.
 */

void
SetSize (Param val)
{
	SpitSetting ("point-size", curSize, val, Units (1, 'x'));
	prevSize = curSize;
	curSize = val;
	StrSetRegisterValue (".s", curSize);
}


void
SetSpaceSize (Param val)
{
	SpitSetting ("space-size", curSpaceSize, val, Units (0, 'x'));
	curSpaceSize = val;
}


/*
 * groff allows page offset to be set negative, but no other
 * troff/nroff/*roff I've found does so, so value is clipped
 * to zero here.
 */

void
SetOffset (Param val)
{
	val = Max (val, Units (0, 'i'));	/* can't set negative */
	SpitSetting ("offset", curOffset, val, Units (0, 'i'));
	prevOffset = curOffset;
	curOffset = val;
	StrSetRegisterValue (".o", curOffset);
}


void
SetSpacing (Param val)
{
	SpitSetting ("spacing", curVSize, val, Units (1, 'p'));
	prevVSize = curVSize;
	curVSize = val;
	StrSetRegisterValue (".v", curVSize);
}


void
SetLineSpacing (Param val)
{
	val = Max (val, Units (1, 'x'));	/* can't set < 1 */
	SpitSetting ("line-spacing", curLineSpacing, val, Units (1, 'x'));
	prevLineSpacing = curLineSpacing;
	curLineSpacing = val;
	StrSetRegisterValue (".L", curLineSpacing);
}


void
SetLineLength (Param val)
{
	val = Max (val, Units (0.1, 'i'));	/* can't set < .1i */
	SpitSetting ("line-length", curLineLen, val, Units (0.1, 'i'));
	prevLineLen = curLineLen;
	curLineLen = val;
	StrSetRegisterValue (".l", curLineLen);
}


/*
 * Write indentation value.  Cannot indent left to a negative value.
 *
 * Pass val+1 for "current" value because it's possible that, although the
 * indent may not actually have changed, the temp indent was set.  If so,
 * the indent has to be forced out to override that.  So force it to print
 * on every occurrence.
 */

void
SetIndent (Param val)
{
	val = Max (val, Units (0, 'i'));	/* can't set negative */
	SpitSetting ("indent", val + 1, val, Units (0, 'i'));
	prevIndent = curIndent;
	curIndent = val;
	StrSetRegisterValue (".i", curIndent);
}


/*
 * Set temporary indent.  This is written out as \temp-indent n, where
 * n is an absolute value.  When .ti is given a relative argument, the
 * resulting indent is calculated relative to current indent, not the
 * current temp indent (this happens by specifying the indent amount
 * in the actions file as the value to use for relative settings).
 *
 * Successive temp-indents before next output line don't cascade; each
 * overrides the previous one.
 *
 * If a "regular" indent is set after a temp indent and before the next
 * text, the regular indent cancels the temp indent.
 *
 * Cannot temp-indent left to a negative value.
 *
 * Pass val+1 for "current" value because this parameter has
 * an effect only transiently and we can't tell whether it's
 * still in effect or not.  So force it to print on every
 * occurrence.
 */

void
SetTempIndent (Param val)
{
	SpitSetting ("temp-indent", val + 1, val, Units (0, 'i'));
	curTempIndent = val;
}


void
SetHyphenation (Param val)
{
char	buf[bufSiz];

	if (val != curHyphenMode)
	{
		sprintf (buf, "hyphenate %ld", val);
		ControlOut (buf);
	}
	curHyphenMode = val;
}


void
SetTitleLength (Param val)
{
	val = Max (val, Units (0, 'i'));	/* can't set negative */
	SpitSetting ("title-length", curTitleLen, val, Units (0, 'i'));
	prevTitleLen = curTitleLen;
	curTitleLen = val;
}


void
SetPageNumber (Param val)
{
	SpitSetting ("page-number", curPageNum, val, Units (-99999, 'x'));
	StrSetRegisterValue ("%", curPageNum = val);
}


void
SetTabStops (Param n, Param *pos, char *type)
{
char	buf[bufSiz];
int	same = 1;	/* assume all same until know otherwise */
int	i;

	if (n != curTabCount)
		same = 0;
	else for (i = 0; i < curTabCount; i++)
	{
		if (pos[i] != tabPos[i] || type[i] != tabType[i])
		{
			same = 0;
			break;
		}
	}
	if (same)
		return;		/* no output necessary */
	if ((curTabCount = n) == 0)
		ControlOut ("reset-tabs");
	else for (i = 0; i < curTabCount; i++)
	{
		tabPos[i] = pos[i];
		tabType[i] = type[i];
		sprintf (buf, "%s-tab %ld %c",
				i == 0 ? "first" : "next",
				tabPos[i], tabType[i]);
		ControlOut (buf);
	}
}


/*
 * Set compatibility mode, returning previous value
 */

Param
SetCompatMode (Param newCompat)
{
Param	prevCompat = compatMode;

	compatMode = newCompat;
	StrSetRegisterValue (".C", compatMode);
	return (prevCompat);
}
