/*
 * troff4.c
 *
 * number registers, conversion, arithmetic
 */

#include "tdef.h"
#include "fns.h"
#include "ext.h"

int	regcnt = NNAMES;
int	falsef	= 0;	/* on if inside false branch of if */

#define	NHASHSIZE	128	/* must be 2**n */
#define	NHASH(i)	((i>>6)^i) & (NHASHSIZE-1)
Numtab	*nhash[NHASHSIZE];

Numtab *numtabp = NULL;
#define NDELTA 400
int ncnt = 0;

void setn(void)
{
	int i, j, f;
	Tchar ii;
	Uchar *p;
	char buf[NTM];		/* for \n(.S */

	f = nform = 0;
	if ((i = cbits(ii = getach())) == '+')
		f = 1;
	else if (i == '-')
		f = -1;
	else if (ii)	/* don't put it back if it's already back (thanks to jaap) */
		ch = ii;
	if (falsef)
		f = 0;
	if ((i = getsn()) == 0)
		return;
	p = unpair(i);
	if (p[0] == '.')
		switch (p[1]) {
		case 's':
			i = pts;
			break;
		case 'v':
			i = lss;
			break;
		case 'f':
			i = font;
			break;
		case 'p':
			i = pl;
			break;
		case 't':
			i = findt1();
			break;
		case 'o':
			i = po;
			break;
		case 'l':
			i = ll;
			break;
		case 'i':
			i = in;
			break;
		case '$':
			i = frame->nargs;
			break;
		case 'A':
			i = ascii;
			break;
		case 'c':
			i = numtabp[CD].val;
			break;
		case 'n':
			i = lastl;
			break;
		case 'a':
			i = ralss;
			break;
		case 'h':
			i = dip->hnl;
			break;
		case 'd':
			if (dip != d)
				i = dip->dnl;
			else
				i = numtabp[NL].val;
			break;
		case 'u':
			i = fi;
			break;
		case 'j':
			i = ad + 2 * admod;
			break;
		case 'w':
			i = widthp;
			break;
		case 'x':
			i = nel;
			break;
		case 'y':
			i = un;
			break;
		case 'T':
			i = dotT;
			break;	 /* -Tterm used in nroff */
		case 'V':
			i = VERT;
			break;
		case 'H':
			i = HOR;
			break;
		case 'k':
			i = ne;
			break;
		case 'P':
			i = doprint;
			break;
		case 'L':
			i = ls;
			break;
		case 'R':	/* maximal # of regs that can be addressed */
			i = 255*256 - regcnt; 
			break;
		case 'z':
			p = unpair(dip->curd);
			*pbp++ = p[1];	/* watch order */
			*pbp++ = p[0];
			return;
		case 'b':
			i = bdtab[font];
			break;
		case 'F':
			cpushback(cfname[ifi]);
			return;
 		case 'S':
 			buf[0] = j = 0;	
 			for( i = 0; tabtab[i] != 0 && i < NTAB; i++) {
 				if (i > 0)
 					buf[j++] = ' ';
 				sprintf(&buf[j], "%d", tabtab[i] & TABMASK);
 				j = strlen(buf);
 				if ( tabtab[i] & RTAB)
 					sprintf(&buf[j], "uR");
 				else if (tabtab[i] & CTAB)
 					sprintf(&buf[j], "uC");
 				else
 					sprintf(&buf[j], "uL");
 				j += 2;
 			}
 			cpushback(buf);
 			return;
		default:
			goto s0;
		}
	else {
s0:
		if ((j = findr(i)) == -1)
			i = 0;
		else {
			i = numtabp[j].val = numtabp[j].val + numtabp[j].inc * f;
			nform = numtabp[j].fmt;
		}
	}
	setn1(i, nform, (Tchar) 0);
}

Tchar	numbuf[25];
Tchar	*numbufp;

int wrc(Tchar i)
{
	if (numbufp >= &numbuf[24])
		return(0);
	*numbufp++ = i;
	return(1);
}



/* insert into input number i, in format form, with size-font bits bits */
void setn1(int i, int form, Tchar bits)
{
	numbufp = numbuf;
	nrbits = bits;
	nform = form;
	fnumb(i, wrc);
	*numbufp = 0;
	pushback(numbuf);
}

void prnumtab(Numtab *p)
{
	int i;
	for (i = 0; i < ncnt; i++)
		if (p)
			if (p[i].r != 0)
				fprintf(stderr, "slot %d, %s, val %d\n", i, unpair(p[i].r), p[i].val);
			else
				fprintf(stderr, "slot %d empty\n", i);
		else
			fprintf(stderr, "slot %d empty\n", i);
}

void nnspace(void)
{
	ncnt = sizeof(numtab)/sizeof(Numtab) + NDELTA;
	numtabp = (Numtab *) grow((char *)numtabp, ncnt, sizeof(Numtab));
	if (numtabp == NULL) {
		ERROR "not enough memory for registers (%d)", ncnt WARN;
		exit(1);
	}
	numtabp = (Numtab *) memcpy((char *)numtabp, (char *)numtab,
							sizeof(numtab));
	if (numtabp == NULL) {
		ERROR "Cannot initialize registers" WARN;
		exit(1);
	}
}

void grownumtab(void)
{
	ncnt += NDELTA;
	numtabp = (Numtab *) grow((char *) numtabp, ncnt, sizeof(Numtab));
	if (numtabp == NULL) {
		ERROR "Too many number registers (%d)", ncnt WARN;
		done2(04);
	} else {
		memset((char *)(numtabp) + (ncnt - NDELTA) * sizeof(Numtab),
						0, NDELTA * sizeof(Numtab));
		nrehash();
	}
}

void nrehash(void)
{
	Numtab *p;
	int i;

	for (i=0; i<NHASHSIZE; i++)
		nhash[i] = 0;
	for (p=numtabp; p < &numtabp[ncnt]; p++)
		p->link = 0;
	for (p=numtabp; p < &numtabp[ncnt]; p++) {
		if (p->r == 0)
			continue;
		i = NHASH(p->r);
		p->link = nhash[i];
		nhash[i] = p;
	}
}

void nunhash(Numtab *rp)
{
	Numtab *p;
	Numtab **lp;

	if (rp->r == 0)
		return;
	lp = &nhash[NHASH(rp->r)];
	p = *lp;
	while (p) {
		if (p == rp) {
			*lp = p->link;
			p->link = 0;
			return;
		}
		lp = &p->link;
		p = p->link;
	}
}

int findr(int i)
{
	Numtab *p;
	int h = NHASH(i);

	if (i == 0)
		return(-1);
a0:
	for (p = nhash[h]; p; p = p->link)
		if (i == p->r)
			return(p - numtabp);
	for (p = numtabp; p < &numtabp[ncnt]; p++) {
		if (p->r == 0) {
			p->r = i;
			p->link = nhash[h];
			nhash[h] = p;
			regcnt++;
			return(p - numtabp);
		}
	}
	grownumtab();
	goto a0;
}

int usedr(int i)	/* returns -1 if nr i has never been used */
{
	Numtab *p;

	if (i == 0)
		return(-1);
	for (p = nhash[NHASH(i)]; p; p = p->link)
		if (i == p->r)
			return(p - numtabp);
	return -1;
}


int fnumb(int i, int (*f)(Tchar))
{
	int j;

	j = 0;
	if (i < 0) {
		j = (*f)('-' | nrbits);
		i = -i;
	}
	switch (nform) {
	default:
	case '1':
	case 0:
		return decml(i, f) + j;
	case 'i':
	case 'I':
		return roman(i, f) + j;
	case 'a':
	case 'A':
		return abc(i, f) + j;
	}
}


int decml(int i, int (*f)(Tchar))
{
	int j, k;

	k = 0;
	nform--;
	if ((j = i / 10) || (nform > 0))
		k = decml(j, f);
	return(k + (*f)((i % 10 + '0') | nrbits));
}


int roman(int i, int (*f)(Tchar))
{

	if (!i)
		return((*f)('0' | nrbits));
	if (nform == 'i')
		return(roman0(i, f, "ixcmz", "vldw"));
	else
		return(roman0(i, f, "IXCMZ", "VLDW"));
}


int roman0(int i, int (*f)(Tchar), char *onesp, char *fivesp)
{
	int q, rem, k;

	if (!i)
		return(0);
	k = roman0(i / 10, f, onesp + 1, fivesp + 1);
	q = (i = i % 10) / 5;
	rem = i % 5;
	if (rem == 4) {
		k += (*f)(*onesp | nrbits);
		if (q)
			i = *(onesp + 1);
		else
			i = *fivesp;
		return(k + (*f)(i | nrbits));
	}
	if (q)
		k += (*f)(*fivesp | nrbits);
	while (--rem >= 0)
		k += (*f)(*onesp | nrbits);
	return(k);
}


int abc(int i, int (*f)(Tchar))
{
	if (!i)
		return((*f)('0' | nrbits));
	else
		return(abc0(i - 1, f));
}


int abc0(int i, int (*f)(Tchar))
{
	int j, k;

	k = 0;
	if (j = i / 26)
		k = abc0(j - 1, f);
	return(k + (*f)((i % 26 + nform) | nrbits));
}

long atoi0(void)
{
	int c, k, cnt;
	Tchar ii;
	long i, acc;

	acc = 0;
	nonumb = 0;
	cnt = -1;
a0:
	cnt++;
	ii = getch();
	c = cbits(ii);
	switch (c) {
	default:
		ch = ii;
		if (cnt)
			break;
	case '+':
		i = ckph();
		if (nonumb)
			break;
		acc += i;
		goto a0;
	case '-':
		i = ckph();
		if (nonumb)
			break;
		acc -= i;
		goto a0;
	case '*':
		i = ckph();
		if (nonumb)
			break;
		acc *= i;
		goto a0;
	case '/':
		i = ckph();
		if (nonumb)
			break;
		if (i == 0) {
			flusho();
			ERROR "divide by zero." WARN;
			acc = 0;
		} else
			acc /= i;
		goto a0;
	case '%':
		i = ckph();
		if (nonumb)
			break;
		acc %= i;
		goto a0;
	case '&':	/*and*/
		i = ckph();
		if (nonumb)
			break;
		if ((acc > 0) && (i > 0))
			acc = 1;
		else
			acc = 0;
		goto a0;
	case ':':	/*or*/
		i = ckph();
		if (nonumb)
			break;
		if ((acc > 0) || (i > 0))
			acc = 1;
		else
			acc = 0;
		goto a0;
	case '=':
		if (cbits(ii = getch()) != '=')
			ch = ii;
		i = ckph();
		if (nonumb) {
			acc = 0;
			break;
		}
		if (i == acc)
			acc = 1;
		else
			acc = 0;
		goto a0;
	case '>':
		k = 0;
		if (cbits(ii = getch()) == '=')
			k++;
		else
			ch = ii;
		i = ckph();
		if (nonumb) {
			acc = 0;
			break;
		}
		if (acc > (i - k))
			acc = 1;
		else
			acc = 0;
		goto a0;
	case '<':
		k = 0;
		if (cbits(ii = getch()) == '=')
			k++;
		else
			ch = ii;
		i = ckph();
		if (nonumb) {
			acc = 0;
			break;
		}
		if (acc < (i + k))
			acc = 1;
		else
			acc = 0;
		goto a0;
	case ')':
		break;
	case '(':
		acc = atoi0();
		goto a0;
	}
	return(acc);
}


long ckph(void)
{
	Tchar i;

	if (cbits(i = getch()) == '(')
		return atoi0();
	else
		return atoi1(i);
}


/*
 * print error about illegal numeric argument;
 */
void prnumerr(void)
{
	char err_buf[128];
	static char warn[] = "Numeric argument expected";
	int savcd = numtabp[CD].val;

	if (numerr.type == RQERR)
		snprintf(err_buf, sizeof err_buf, "%c%s: %s",
			nb ? cbits(c2) : cbits(cc), unpair(numerr.req), warn);
	else
		snprintf(err_buf, sizeof err_buf, "\\%c'%s': %s",
			numerr.esc, &numerr.escarg,
									warn);
	if (frame != stk)	/* uncertainty correction */
		numtabp[CD].val--;
	ERROR err_buf WARN;
	numtabp[CD].val = savcd;
}


long atoi1(Tchar ii)
{
	int i, j, digits;
	double acc;	/* this is the only double in troff! */
	int neg, abs, field, decpnt;
	extern int ifnum;

	neg = abs = field = decpnt = digits = 0;
	acc = 0;
	for (;;) {
		i = cbits(ii);
		switch (i) {
		default:
			break;
		case '+':
			ii = getch();
			continue;
		case '-':
			neg = 1;
			ii = getch();
			continue;
		case '|':
			abs = 1 + neg;
			neg = 0;
			ii = getch();
			continue;
		}
		break;
	}
a1:
	while (i >= '0' && i <= '9') {
		field++;
		digits++;
		acc = 10 * acc + i - '0';
		ii = getch();
		i = cbits(ii);
	}
	if (i == '.' && !decpnt++) {
		field++;
		digits = 0;
		ii = getch();
		i = cbits(ii);
		goto a1;
	}
	if (!field) {
		ch = ii;
		goto a2;
	}
	switch (i) {
	case 'u':
		i = j = 1;	/* should this be related to HOR?? */
		break;
	case 'v':	/*VSs - vert spacing*/
		j = lss;
		i = 1;
		break;
	case 'm':	/*Ems*/
		j = EM;
		i = 1;
		break;
	case 'n':	/*Ens*/
		j = EM;
		if (TROFF)
			i = 2;
		else
			i = 1;	/*Same as Ems in NROFF*/
		break;
	case 'p':	/*Points*/
		j = INCH;
		i = 72;
		break;
	case 'i':	/*Inches*/
		j = INCH;
		i = 1;
		break;
	case 'c':	/*Centimeters*/
		/* if INCH is too big, this will overflow */
		j = INCH * 50;
		i = 127;
		break;
	case 'P':	/*Picas*/
		j = INCH;
		i = 6;
		break;
	default:
		j = dfact;
		ch = ii;
		i = dfactd;
	}
	if (neg)
		acc = -acc;
	if (!noscale) {
		acc = (acc * j) / (i? i: 1);
	}
	if (field != digits && digits > 0)
		while (digits--)
			acc /= 10;
	if (abs) {
		if (dip != d)
			j = dip->dnl;
		else
			j = numtabp[NL].val;
		if (!vflag) {
			j = numtabp[HP].val;
		}
		if (abs == 2)
			j = -j;
		acc -= j;
	}
a2:
	nonumb = (!field || field == decpnt);
	if (nonumb && (trace & TRNARGS) && !ismot(ii) && !nlflg && !ifnum) {
		if (cbits(ii) != RIGHT ) /* Too painful to do right */
			prnumerr();
	}
	return(acc);
}


void caserr(void)
{
	int i, j;
	Numtab *p;

	lgf++;
	while (!skip() && (i = getrq()) ) {
		j = usedr(i);
		if (j < 0)
			continue;
		p = &numtabp[j];
		nunhash(p);
		p->r = p->val = p->inc = p->fmt = 0;
		regcnt--;
	}
}

/*
 * .nr request; if tracing, don't check optional
 * 2nd argument because tbl generates .in 1.5n
 */
void casenr(void)
{
	int i, j;
	int savtr = trace;

	lgf++;
	skip();
	if ((i = findr(getrq())) == -1)
		return;
	skip();
	j = inumb(&numtabp[i].val);
	if (nonumb)
		return;
	numtabp[i].val = j;
	skip();
	trace = 0;
	j = atoi0();		/* BUG??? */
	trace = savtr;
	if (nonumb)
		return;
	numtabp[i].inc = j;
}

void caseaf(void)
{
	int i, k;
	Tchar j;

	lgf++;
	if (skip() || !(i = getrq()) || skip())
		return;
	k = 0;
	j = getch();
	if (!isalpha(cbits(j))) {
		ch = j;
		while ((j = cbits(getch())) >= '0' &&  j <= '9')
			k++;
	}
	if (!k)
		k = j;
	numtabp[findr(i)].fmt = k;	/* was k & BYTEMASK */
}

void setaf(void)	/* return format of number register */
{
	int i, j;

	i = usedr(getsn());
	if (i == -1)
		return;
	if (numtabp[i].fmt > 20)	/* it was probably a, A, i or I */
		*pbp++ = numtabp[i].fmt;
	else
		for (j = (numtabp[i].fmt ? numtabp[i].fmt : 1); j; j--)
			*pbp++ = '0';
}


int vnumb(int *i)
{
	vflag++;
	dfact = lss;
	res = VERT;
	return(inumb(i));
}


int hnumb(int *i)
{
	dfact = EM;
	res = HOR;
	return(inumb(i));
}


int inumb(int *n)
{
	int i, j, f;
	Tchar ii;

	f = 0;
	if (n) {
		if ((j = cbits(ii = getch())) == '+')
			f = 1;
		else if (j == '-')
			f = -1;
		else
			ch = ii;
	}
	i = atoi0();
	if (n && f)
		i = *n + f * i;
	i = quant(i, res);
	vflag = 0;
	res = dfactd = dfact = 1;
	if (nonumb)
		i = 0;
	return(i);
}


int quant(int n, int m)
{
	int i, neg;

	neg = 0;
	if (n < 0) {
		neg++;
		n = -n;
	}
	/* better as i = ((n + m/2)/m)*m */
	if (m != 0)
		i = n / m;
	else
		i = n;
	if (n - m * i > m / 2)
		i += 1;
	i *= m;
	if (neg)
		i = -i;
	return(i);
}
