#include	"misc.h"
#include	"slug.h"
#include	<math.h>

static char	*bufptr(int);

void slug::coalesce()
{
	(this+1)->dp = dp;	// pretty grimy, but meant to ensure
				// that all output goes out.
			// maybe it has to skip over PT's;
			// some stuff is getting pushed inside PT..END
}

void slug::neutralize()
{
	switch (type) {
	case PAGE:
	case UF:
	case BF:
	case PARM:
		type = NEUTRAL;
		coalesce();
		break;
	default:
		ERROR "neutralized %d (%s) with %s\n",
			type, typename(), headstr() WARNING;
		break;
	}
}

void slug::dump()	// print contents of a slug
{
	printf("# %d %-4.4s parm %d dv %d base %d s%d f%d H%d\n#\t\t%s\n",
		serialno(), typename(), parm, dv, base,
		size, font, hpos, headstr());
}

char *slug::headstr()
{
	const int HEADLEN = 65;
	static char buf[2*HEADLEN];
	int j = 0;
	char *s = bufptr(dp);
	int n = (this+1)->dp - dp;
	if (n >= HEADLEN)
		n = HEADLEN;
	for (int i = 0; i < n; i++)
		switch (s[i]) {
			case '\n':
			case '\t':
			case '\0':
			case ' ':
				break;
			default:
				buf[j++] = s[i];
				break;
		}
	buf[j] = 0;
	return buf;
}

static char *strindex(char s[], char t[])	// index of earliest t[] in s[]
{
	for (int i = 0; s[i] != '\0'; i++) {
		int j, k;
		for (j = i, k = 0; t[k]!='\0' && s[j] == t[k]; j++, k++)
			;
		if (k > 0 && t[k] == '\0')
			return s+i;
	}
	return 0;
}

void slug::slugout(int col)
{
	static int numout = 0;
	if (seen++)
		ERROR "%s slug #%d seen %d times [%s]\n",
			typename(), serialno(), seen, headstr() WARNING;
	if (type == TM) {
		char *p;
		if ((p = strindex(bufptr(dp), "x X TM ")) != 0)
			p += strlen("x X TM ");		// skip junk
		else
			ERROR "strange TM [%s]\n", headstr() FATAL;
		fprintf(stderr, "%d\t", userpn);	// page # as prefix
		for ( ; p < bufptr((this+1)->dp); p++)
			putc(*p, stderr);
	} else if (type == COORD) {
		for (char *p = bufptr(dp); p < bufptr((this+1)->dp) && *p != '\n'; p++)
			putc(*p, stdout);
		printf(" # P %d X %d", userpn, hpos + col*offset);
		return;
	} else if (type == VBOX) {
		if (numout++ > 0)	// BUG??? might miss something
			printf("s%d\nf%d\n", size, font);
		printf("H%d\n", hpos + col*offset);
	}
	fwrite(bufptr(dp), sizeof(char), (this+1)->dp - dp, stdout);
}

char *slug::typename()
{
	static char buf[50];
	char *p = buf;		// return value
	switch(type) {
	case EOF:	p = "EOF"; break;
	case VBOX:	p = "VBOX"; break;
	case SP:	p = "SP"; break;
	case BS:	p = "BS"; break;
	case US:	p = "US"; break;
	case BF:	p = "BF"; break;
	case UF:	p = "UF"; break;
	case PT:	p = "PT"; break;
	case BT:	p = "BT"; break;
	case END:	p = "END"; break;
	case NEUTRAL:	p = "NEUT"; break;
	case PAGE:	p = "PAGE"; break;
	case TM:	p = "TM"; break;
	case COORD:	p = "COORD"; break;
	case NE:	p = "NE"; break;
	case CMD:	p = "CMD"; break;
	case PARM:	p = "PARM"; break;
	default:	sprintf(buf, "weird type %d", type);
	}
	return p;
}

// ================================================================================

// 	troff output-specific functions

// ================================================================================

const int	DELTABUF = 500000;	// grow the input buffer in chunks

static char	*inbuf = 0;		// raw text input collects here
static int	ninbuf = 0;		// byte count for inbuf
static char	*inbp = 0;		// next free slot in inbuf
int		linenum = 0;		// input line number

static inline void addc(int c) { *inbp++ = c; }

static void adds(char *s)
{
	for (char *p = s; *p; p++)
		addc(*p);
}

static int fullrune(char *c, int n)
{
	if(n <= 0)
		return 0;
	if(n>=1 && (unsigned char)c[0] < 0x80)
		return 1;
	if(n>=2 && (unsigned char)c[0] < 0xE0)
		return 1;
	if(n>=3)
		return 1;
	return 0;
}

static char *getutf(FILE *fp)	// get 1 utf-encoded char (might be multiple bytes)
{
	static char buf[100];
	char *p = buf;

	for (*p = 0; (*p++ = getc(fp)) != EOF; ) {
		*p = 0;
		if (fullrune(buf, p-buf))	// found a valid character
			break;
	}
	return buf;
}

static char *bufptr(int n) { return inbuf + n; }  // scope of inbuf is too local

static inline int wherebuf() { return inbp - inbuf; }

static char *getstr(char *p, char *temp)
{		// copy next non-blank string from p to temp, update p
	while (*p == ' ' || *p == '\t' || *p == '\n')
		p++;
	if (*p == '\0') {
		temp[0] = 0;
		return(NULL);
	}
	while (*p != ' ' && *p != '\t' && *p != '\n' && *p != '\0')
		*temp++ = *p++;
	*temp = '\0';
	return(p);
}

/***************************************************************************
   bounding box of a circular arc             Eric Grosse  24 May 84

Conceptually, this routine generates a list consisting of the start,
end, and whichever north, east, south, and west points lie on the arc.
The bounding box is then the range of this list.
    list = {start,end}
    j = quadrant(start)
    k = quadrant(end)
    if( j==k && long way 'round )  append north,west,south,east
    else
      while( j != k )
         append center+radius*[j-th of north,west,south,east unit vectors]
         j += 1  (mod 4)
    return( bounding box of list )
The following code implements this, with simple optimizations.
***********************************************************************/

static int quadrant(double x, double y)
{
	if (     x>=0.0 && y> 0.0) return(1);
	else if( x< 0.0 && y>=0.0) return(2);
	else if( x<=0.0 && y< 0.0) return(3);
	else if( x> 0.0 && y<=0.0) return(4);
	else			   return 0;	/* shut up lint */
}

static double xmin, ymin, xmax, ymax;	// used by getDy

static void arc_extreme(double x0, double y0, double x1, double y1, double xc, double yc)
		/* start, end, center */
{		/* assumes center isn't too far out */
	double r;
	int j, k;
	printf("#start %g,%g, end %g,%g, ctr %g,%g\n", x0,y0, x1,y1, xc,yc);
	y0 = -y0; y1 = -y1; yc = -yc;	// troff's up is eric's down
	x0 -= xc; y0 -= yc;	/* move to center */
	x1 -= xc; y1 -= yc;
	xmin = (x0<x1)?x0:x1; ymin = (y0<y1)?y0:y1;
	xmax = (x0>x1)?x0:x1; ymax = (y0>y1)?y0:y1;
	r = sqrt(x0*x0 + y0*y0);
	if (r > 0.0) {
		j = quadrant(x0,y0);
		k = quadrant(x1,y1);
		if (j == k && y1*x0 < x1*y0) {
			/* viewed as complex numbers, if Im(z1/z0)<0, arc is big */
			if( xmin > -r) xmin = -r; if( ymin > -r) ymin = -r;
			if( xmax <  r) xmax =  r; if( ymax <  r) ymax =  r;
		} else {
			while (j != k) {
				switch (j) {
				case 1: if( ymax <  r) ymax =  r; break; /* north */
				case 2: if( xmin > -r) xmin = -r; break; /* west */
				case 3: if( ymin > -r) ymin = -r; break; /* south */
				case 4: if( xmax <  r) xmax =  r; break; /* east */
				}
				j = j%4 + 1;
			}
		}
	}
	xmin += xc; ymin += yc; ymin = -ymin;
	xmax += xc; ymax += yc; ymax = -ymax;
}


static int getDy(char *p, int *dx, int *maxv)
				// figure out where we are after a D'...'
{
	int x, y, x1, y1;	// for input values
	char temp[50];
	p++;		// get to command letter
	switch (*p++) {
	case 'l':	// line
		sscanf(p, "%d %d", dx, &y);
		return *maxv = y;
	case 'a':	// arc
		sscanf(p, "%d %d %d %d", &x, &y, &x1, &y1);
		*dx = x1 - x;
		arc_extreme(0, 0, x+x1, y+y1, x, y);	// sets [xy][max|min]
		printf("#arc bounds x %g, %g; y %g, %g\n",
			xmin, xmax, ymin, ymax);
		*maxv = (int) (ymin+0.5);
		return y + y1;
	case '~':	// spline
		for (*dx = *maxv = y = 0; (p=getstr(p, temp)) != NULL; ) {
						// above getstr() gets x value
			*dx += atoi(temp);
			p = getstr(p, temp);	// this one gets y value
			y += atoi(temp);
			*maxv = max(*maxv, y);	// ok???
			if (*p == '\n' || *p == 0)	// input is a single line;
				break;			// don't walk off end if realloc
		}
		return y;
	case 'c':	// circle, ellipse
		sscanf(p, "%d", dx);
		*maxv = *dx/2;		// high water mark is ht/2
		return 0;
	case 'e':
		sscanf(p, "%d %d", dx, &y);
		*maxv = y/2;		// high water mark is ht/2
		return 0;
	default:	// weird stuff
		return 0;
	}
}

static int serialnum = 0;

slug eofslug()
{
	slug ret;
	ret.serialnum = serialnum;
	ret.type = EOF;
	ret.dp = wherebuf();
	return ret;
}

slug getslug(FILE *fp)
{
	if (inbuf == NULL) {
		if ((inbuf = (char *) malloc(ninbuf = DELTABUF)) == NULL)
			ERROR "no room for %d character input buffer\n", ninbuf FATAL;
		inbp = inbuf;
	}
	if (wherebuf() > ninbuf-5000) {
		// this is still flaky -- lines can be very long
		int where = wherebuf();	// where we were
		if ((inbuf = (char *) realloc(inbuf, ninbuf += DELTABUF)) == NULL)
			ERROR "no room for %d character input buffer\n", ninbuf FATAL;
		ERROR "grew input buffer to %d characters\n", ninbuf WARNING;
		inbp = inbuf + where;	// same offset in new array
	}
	static int baseV = 0;	// first V command of preceding slug
	static int curV = 0, curH = 0;
	static int font = 0, size = 0;
	static int baseadj = 0;
	static int ncol = 1, offset = 0;	// multi-column stuff
	char str[1000], str2[1000], buf[3000], *p;
	int firstV = 0, firstH = 0;
	int maxV = curV;
	int ocurV = curV, mxv = 0, dx = 0;
	int sawD = 0;		// > 0 if have seen D...
	slug ret;
	ret.serialnum = serialnum++;
	ret.type = VBOX;	// use the same as last by default
	ret.dv = curV - baseV;
	ret.hpos = curH;
	ret.base =  ret.parm = ret.parm2 = ret.seen = 0;
	ret.font = font;
	ret.size = size;
	ret.dp = wherebuf();
	ret.ncol = ncol;
	ret.offset = offset;
	ret.linenum = linenum;	// might be low

	for (;;) {
		int c, m, n;	// for input values
		int sign;		// hoisted from case 'h' below
		switch (c = getc(fp)) {
		case EOF:
			ret.type = EOF;
			ret.dv = 0;
			if (baseadj)
				printf("# adjusted %d bases\n", baseadj);
			printf("# %d characters, %d lines\n", wherebuf(), linenum);
			return ret;
		case 'V':
			fscanf(fp, "%d", &n);
			if (firstV++ == 0) {
				ret.dv = n - baseV;
				baseV = n;
			} else {
				sprintf(buf, "v%d", n - curV);
				adds(buf);
			}
			curV = n;
			maxV = max(maxV, curV);
			break;
		case 'H':		// absolute H motion
			fscanf(fp, "%d", &n);
			if (firstH++ == 0) {
				ret.hpos = n;
			} else {
				sprintf(buf, "h%d", n - curH);
				adds(buf);
			}
			curH = n;
			break;
		case 'h':		// relative H motion
			addc(c);
			sign = 1;
			if ((c = getc(fp)) == '-') {
				addc(c);
				sign = -1;
				c = getc(fp);
			}
			for (n = 0; isdigit(c); c = getc(fp)) {
				addc(c);
				n = 10 * n + c - '0';
			}
			curH += n * sign;
			ungetc(c, fp);
			break;
		case 'x':	// device control: x ...
			addc(c);
			fgets(buf, (int) sizeof(buf), fp);
			linenum++;
			adds(buf);
			if (buf[0] == ' ' && buf[1] == 'X') {	// x X ...
				if (2 != sscanf(buf+2, "%s %d", str, &n))
					n = 0;
				if (eq(str, "SP")) {	// X SP n
					ret.type = SP;	// paddable SPace
					ret.dv = n;	// of height n
				} else if (eq(str, "BS")) {
					ret.type = BS;	// Breakable Stream
					ret.parm = n;	// >=n VBOXES on a page
				} else if (eq(str, "BF")) {
					ret.type = BF;	// Breakable Float
					ret.parm = ret.parm2 = n;
							// n = pref center (as UF)
				} else if (eq(str, "US")) {
					ret.type = US;	// Unbreakable Stream
					ret.parm = n;
				} else if (eq(str, "UF")) {
					ret.type = UF;	// Unbreakable Float
					ret.parm = ret.parm2 = n;
							// n = preferred center
							// to select several,
							// use several UF lines
				} else if (eq(str, "PT")) {
					ret.type = PT;	// Page Title
					ret.parm = n;
				} else if (eq(str, "BT")) {
					ret.type = BT;	// Bottom Title
					ret.parm = n;
				} else if (eq(str, "END")) {
					ret.type = END;
					ret.parm = n;
				} else if (eq(str, "TM")) {
					ret.type = TM;	// Terminal Message
					ret.dv = 0;
				} else if (eq(str, "COORD")) {
					ret.type = COORD;// page COORDinates
					ret.dv = 0;
				} else if (eq(str, "NE")) {
					ret.type = NE;	// NEed to break page
					ret.dv = n;	// if <n units left
				} else if (eq(str, "MC")) {
					ret.type = MC;	// Multiple Columns
					sscanf(buf+2, "%s %d %d",
						str, &ncol, &offset);
					ret.ncol = ncol;
					ret.offset = offset;
				} else if (eq(str, "CMD")) {
					ret.type = CMD;	// CoMmaNd
					sscanf(buf+2, "%s %s", str2, str);
					if (eq(str, "FC"))	// Freeze 2-Col
						ret.parm = FC;
					else if (eq(str, "FL"))	// FLush
						ret.parm = FL;
					else if (eq(str, "BP"))	// Break Page
						ret.parm = BP;
					else ERROR "unknown command %s\n",
						str WARNING;
				} else if (eq(str, "PARM")) {
					ret.type = PARM;// PARaMeter
					sscanf(buf+2, "%s %s %d", str2, str, &ret.parm2);
					if (eq(str, "NP"))	// New Page
						ret.parm = NP;
					else if (eq(str, "FO"))	// FOoter
						ret.parm = FO;
					else if (eq(str, "PL")) // Page Length
						ret.parm = PL;
					else if (eq(str, "MF")) // MinFull
						ret.parm = MF;
					else if (eq(str, "CT")) // ColTol
						ret.parm = CT;
					else if (eq(str, "WARN")) //WARNings?
						ret.parm = WARN;
					else if (eq(str, "DBG"))// DeBuG
						ret.parm = DBG;
					else ERROR "unknown parameter %s\n",
						str WARNING;
				} else
					break;		// out of switch
				if (firstV > 0)
					ERROR "weird x X %s in mid-VBOX\n",
						str WARNING;
				return ret;
			}
			break;
		case 'n':	// end of line
			fscanf(fp, "%d %d", &n, &m);
			ret.ht = n;
			ret.base = m;
			getc(fp);	// newline
			linenum++;
			sprintf(buf, "n%d %d\n", ret.ht, ret.base);
			adds(buf);
			if (!firstV++)
				baseV = curV;
			// older incarnations of this program used ret.base
			// in complicated and unreliable ways;
			// example:  if ret.ht + ret.base < ret.dv, ret.base = 0
			// this was meant to avoid double-counting the space
			// around displayed equations; it didn't work
			// Now, we believe ret.base = 0, otherwise we give it
			// a value we have computed.
			if (ret.base == 0 && sawD == 0)
				return ret;	// don't fiddle 0-bases
			if (ret.base != maxV - baseV) {
				ret.base = maxV - baseV;
				baseadj++;
			}
			if (ret.type != VBOX)
				ERROR "%s slug (type %d) has base = %d\n",
					ret.typename(), ret.type, ret.base WARNING;
			return ret;
		case 'p':	// new page
			fscanf(fp, "%d", &n);
			ret.type = PAGE;
			curV = baseV = ret.dv = 0;
			ret.parm = n;	// just in case someone needs it
			return ret;
		case 's':	// size change snnn
			fscanf(fp, "%d", &size);
			sprintf(buf, "s%d\n", size);
			adds(buf);
			break;
		case 'f':	// font fnnn
			fscanf(fp, "%d", &font);
			sprintf(buf, "f%d\n", font);
			adds(buf);
			break;
		case '\n':
			linenum++;
			/* fall through */
		case ' ':
			addc(c);
			break;
		case '0': case '1': case '2': case '3': case '4':
		case '5': case '6': case '7': case '8': case '9':
			// two motion digits plus a character
			addc(c);
			n = c - '0';
			addc(c = getc(fp));
			curH += 10 * n + c - '0';
			adds(getutf(fp));
			if (!firstV++)
				baseV = curV;
			break;
		case 'c':	// single ascii character
			addc(c);
			adds(getutf(fp));
			if (!firstV++)
				baseV = curV;
			break;
		case 'C':	// Cxyz\n
		case 'N':	// Nnnn\n
			addc(c);
			while ((c = getc(fp)) != ' ' && c != '\n')
				addc(c);
			addc(c);
			if (!firstV++)
				baseV = curV;
			linenum++;
			break;
		case 'D':	// draw function: D.*\n
			sawD++;
			p = bufptr(wherebuf());	// where does the D start
			addc(c);
			while ((c = getc(fp)) != '\n')
				addc(c);
			addc(c);
			if (!firstV++)
				baseV = curV;
			ocurV = curV, mxv = 0, dx = 0;
			curV += getDy(p, &dx, &mxv);	// figure out how big it is
			maxV = max(max(maxV, curV), ocurV+mxv);
			curH += dx;
			linenum++;
			break;
		case 'v':	// relative vertical vnnn
			addc(c);
			if (!firstV++)
				baseV = curV;
			sign = 1;
			if ((c = getc(fp)) == '-') {
				addc(c);
				sign = -1;
				c = getc(fp);
			}
			for (n = 0; isdigit(c); c = getc(fp)) {
				addc(c);
				n = 10 * n + c - '0';
			}
			ungetc(c, fp);
			curV += n * sign;
			maxV = max(maxV, curV);
			addc('\n');
			break;
		case 'w':	// word space
			addc(c);
			break;
		case '#':	// comment
			addc(c);
			while ((c = getc(fp)) != '\n')
				addc(c);
			addc('\n');
			linenum++;
			break;
		default:
			ERROR "unknown input character %o %c (%50.50s)\n",
				c, c, bufptr(wherebuf()-50) WARNING;
			abort();
			break;
		}
	}
}
