#include "fidoterm.h"
#include "mem.h"
#include <ctype.h>
#include <ascii.h>
#include <malloc.h>

void cdecl image_dn(int, int);
void cdecl image_up(int, int);
void cdecl clrln(int, int, int, char, char);
void cdecl backfill(char, char);

/*
;;
;;	FILE:	ANSI1.C
;;
;;
;;
;;ansiout(c)		Type character 'c' on the screen, maintain
;;			in in the screen image.
;;
;;			Process MONITOR mode control characters and 8th bit characters
;;			recursively from here. 
;;			
;;			01h		\01
;;			1ah		\27
;;			81h		!A
;;			255		!\63
;;			\		\\
;;			...		...

;;scrorg();		scrorg() returns the default screen origin, with
;;getorg();		the screen bottom aligned with the logical bottom.
;;setorg();		getorg() and setorg() get and set the current
;;			orogin, respectively.
;;
;;paint(first,last)	Repaint the screen using our screen image,
;;			starting at first, to line last. Paints the
;;			whole screen if the bounds are ridiculous.
;;
;;init_ansi()		Init and clear the screen image. This MUST be
;;			called once before the screen is used.
;;

The lines are kept in a buffer with a table of line pointers; the
pointers get scrolled etc. There are two "origins"; one for output (lporg)
and another for input (deforg). Deforg positions the physical screen lines
at the bottom of the line array. Characters are inserted into the screen
buffer, only in the last N lines of the buffer. Scrolling up causes
lines to move into the upper part of the buffer.

lporg is the point used to paint the screen. It defaults to deforg.
When the two are the same, the physical screen corresponds to the image
constantly maintained in the screen buffer. When lporg is less than deforg,
then the screen is showing "older" text, and characters inserted into the
screen buffer are not displayed.

	Use screen[(line * screen.cols) + col]
*/

struct _cell far * far *lptr;	/* a pointer to line pointer(s) */

static unsigned lporg;		/* display origin of the physical screen */
static unsigned deforg;		/* working screen origin */
static unsigned top,bot;	/* scrolling region limits */

#define ARG_SIZE 10		/* max. number of numeric args */
static int state;		/* escape parser state machine */
static int args[ARG_SIZE];	/* wher args are built */

static int argptr;		/* index into arg array */
static int argcnt;
static char in_digit;		/* if we got a digit, */
static char wrap;		/* true if wrapping over edges allowed */
static char insmode;
static char origin;
static char physflg;		/* 1 == (lporg == deforg) */
static int saved_line,saved_col; /* ESC [ s  and ESC [ u  */


/* Return the default screen origin. */

scrorg() {
	return(deforg);
}

/* Return the current screen origin. */

getorg() {
	return(lporg);
}

/* Set the screen origin. Keep it within the physical array and in non-blank
screen areas. */

setorg(n)
int n;
{
	if (n < 0) n= 0;				/* bound it */
	if (n > deforg) n= deforg;
	lporg= n;					/* set it, */

	while ((lptr[lporg])-> character == 0xff) {	/* keep inside used */
		if (lporg >= deforg) break;		/* screen areas */
		++lporg;				/* or at default */
	}
}

/* Allocate memory for the screen buffer, and determine how many lines we
can hold. There is an array of cell pointers (lptr[]), where each entry is
a pointer to the text for each line; scrolling is done by scrolling the 
pointers. */

init_ansi() {
int n;
struct _cell far *cp;

/*                   (ptr per line)                  (char cells per line) */
#define PER_LINE (sizeof(struct _cell far *) + (screen.cols * sizeof(struct _cell)))

/* NOTE: Keep (n * PER_LINE) < 65535! */

	for (n= 350; n > 100; n--) {		/* alloc space for the screen */
		lptr= (struct _cell far * far *) _fmalloc(n * PER_LINE);
		if (lptr != NULL) break;
	}
	if (n < 100) {
		msg("Not enough memory for screen buffer");
		return(0);
	}

/* lptr[] points to a block of memory large enough to hold N lines,
where each line is a pointer and a lines worth of character cells. The first
part of the block will be the table of pointers, following that will be the
screen cell data. Now we have to initialize the array of pointers to point
into the data area, and init each line as unused, ie. 0xff. */

	lporg= deforg= n - screen.lines;		/* phys. origin */
	cp= (struct _cell far *)(&lptr[n]);		/* lines follow lptr[] */
	while (n--) {					/* initialize lptr[] */
		lptr[n]= cp;				/*   ptr to line text */
		cp-> character= 0xff;			/*   (marked as blank) */
		cp += screen.cols;			/*   next line... */
	}
	state= 0;					/* not parsing */
	wrap= 1;					/* allow wrap */
	origin= 0;
	insmode= 0;

/* Clear the screen and image. */

	physflg= (lporg == deforg);			/* should we do physical output */
	for (line= 0; line < screen.lines; line++) 
		clrline(line,0);

	top= 0; bot= screen.lines - 1;			/* default full screen */
	line= bot; column= 0;				/* Start at bottom line */
	p_pos(line,column);
	p_cursor();
	return(1);
}

/* Type a character on the screen, and save it in our array. */

ansiout(c)
char c;
{
int i;
static char hextable[] = "0123456789abcdef";

	physflg= (lporg == deforg);		/* should we do physical output */
	savchar(c);				/* log characters */

	if (state == 0) switch (termtype) {
		case ANSI:
			if (c == ESC) { 
				state= 1; 
				return; 
			}			/* fall through */
		case FILTER:
			if (c >= ' ') {
				do_ascii(c);
				return;

			} else switch (c) {
				case CR: if (physflg) p_char(CR); column= 0; break;
				case LF: case VT: case FF: dolf(); break;
				case ESC: if (termtype == ANSI) state= 1; else if (physflg) p_char(ESC); break;
				case TAB: do do_ascii(' '); while (column % 8); break;
				case BS: dobs(); break;
				case BEL: /* bdos(6,BEL); */ break; 
				case CAN: if (termtype == ANSI) state= 0; else if (physflg) p_char(CAN); break;
				default: if ((termtype != FILTER) && physflg) p_char(c); state= 0; break;
			}
			if (physflg) {
				p_pos(line,column);	/* set position */
				p_cursor();		/* display cursor */
			}
			return;

		case HEX:
			i= termtype; termtype= FILTER;
			ansiout(hextable[c >> 4]);	/* upper nybble, */
			ansiout(hextable[c & 15]);	/* lower nybble, */
			ansiout(' ');			/* and a space */
			termtype= i;			/* restore terminal type */
			return;

		case MONITOR:
			i= termtype; termtype= FILTER;
			if (c & 0x80) {			/* if 8th bit set */
				ansiout('!');		/* flag it, */
				c &= 0x7f;		/* strip it */
			}
			switch (c) {
				case '\\':		/* the quote character, */
				case '!':		/* the 8th bit character, */
					ansiout('\\');	/* quote it */
					break;

				default:
					if ((c < ' ') || (c > '~')) {
						ansiout('\\');	/* quote it */
						ansiout(c / 100 + '0');
						c %= 100;
						ansiout(c / 10 + '0');
						c %= 10;
						c += '0';
					}
			}
			ansiout(c);		/* finally, output the char */
			termtype= i;
			return;

	} else if (state == 2) {
		switch (c) {
			case '0': case '1': case '2': case '3': case '4':
			case '5': case '6': case '7': case '8': case '9':
				args[argptr]= (args[argptr] * 10) + (c - '0');
				args[argptr] %= 256;
				in_digit= 1;
				break;

			case ';':
				args[++argptr]= 0;
				in_digit= 0;
				break;

			default:
				if (in_digit) ++argptr;
				state= 0;		/* start over */
				do_ansi(c);		/* do command, */
				backfill(' ',screen.def_attribute); /* possibly backfill, */
				if (physflg) p_pos(line,column); /* restore cursor */
				break;
		}
		return;

	} else {
		state= (c == '[' ? 2 : 0); 
		in_digit= 0;
		argptr= 0;
		argcnt= 0;
		args[argptr]= 0;
	}
}

/* Process the ANSI escape sequence; the character passed is the command. */

static do_ansi(c)
char c;
{
int n,i,l;
char a,work[80],*s;

	switch (c) {

/*
	ESC [ pn; pn; ... m	Character Rendition

	Select the character attribute(s) to be used from now on.

	Character attributes, bit mapped:
	
	       +------------------- character blink 
	       |   +--------------- background color/palette
	       |   |   +----------- intensity
	       |   |   |   +------- foreground color/palette
	       |   |   |   |
	       + --+-- + --+--
	       b R G B i R G B


	0	normal attributes
	1	bold
	5	blink
	7	reverse

	FG	BG	
	30	40	black
	31	41	red
	32	42	green
	34	44	blue
	35	45	magenta
	36	46	cyan
	37	47	white

*/
	case 'm': 
		a= screen.attribute;		/* change current attrib */
		while (areargs()) {
			switch (ansiarg()) {	/* not all are acceptable... */

			/* attributes */
				case 0: a= screen.def_attribute; break;
				case 1: a |= 8; break;
				case 5: a |= 128; break;
				case 7: a ^= 0x77; break;

			/* foreground colors */
				case 30: a= (a & 0xf8) | 0; break;	/* black */
				case 31: a= (a & 0xf8) | 4; break;	/* red */
				case 32: a= (a & 0xf8) | 2; break;	/* green */
				case 33: a= (a & 0xf8) | 6; break;	/* orange? */
				case 34: a= (a & 0xf8) | 1; break;	/* blue */
				case 35: a= (a & 0xf8) | 5; break;	/* magenta */
				case 36: a= (a & 0xf8) | 3; break;	/* cyan */
				case 37: a= (a & 0xf8) | 7; break;	/* white */

			/* background colors */
				case 40: a= (a & 0x8f) | (0 << 4); break;	/* black */
				case 41: a= (a & 0x8f) | (4 << 4); break;	/* red */
				case 42: a= (a & 0x8f) | (2 << 4); break;	/* green */
				case 43: a= (a & 0x8f) | (6 << 4); break;	/* orange? */
				case 44: a= (a & 0x8f) | (1 << 4); break;	/* blue */
				case 45: a= (a & 0x8f) | (5 << 4); break;	/* magenta */
				case 46: a= (a & 0x8f) | (3 << 4); break;	/* cyan */
				case 47: a= (a & 0x8f) | (7 << 4); break;	/* white */
			}
		}
		screen.attribute= a;
		break;

/*
	ESC [ pn c

	Request terminal device attributes. Identify ourselves as not a DEC
terminal.
*/
	case 'c':
		sprintf(work,"[F1]");
		for (s= work; *s; s++) modout(*s);
		break;

/* 
	ESC [ pn A	up
	ESC [ pn B	down
	ESC [ pn C	right
	ESC [ pn D	left

Cursor motion. Move the cursor up, down, left or right N times. If no arg,
then N defaults to 1. */

	case 'A':
		for (i= ansiarg(); i--;) {
			if (line <= top) {
				scrolldn(top,bot,physflg);

			} else --line;
		}
		break;

	case 'B':
		for (i= ansiarg(); i--;) {
			if (line >= bot) {
				scrollup(top,line,physflg);

			} else ++line;
		}
		break;

	case 'C':
		for (i= ansiarg(); i--;) {
			if (column >= screen.cols - 1) break;
			++column;
		}
		break;

	case 'D':
		for (i= ansiarg(); i--;) {
			if (column == 0) break;
			--column;
		}
		break;

/* 
	ESC [ pn1 ; pn2 r

Set scrolling region. Sets the top margin to pn1, and the bottom to pn2. If
either is out of bounds it is set to the physical limit; ESC [ r sets
the limits to the power up defaults. */

	case 'r':
		top= ansiarg() - 1;
		bot= ansiarg() - 1;
		if (top > screen.lines - 1) top= 0;
		if (bot > screen.lines - 1) bot= screen.lines - 1;
		if ((bot - top) < 2) {
			top= 0;
			bot= screen.lines - 1;
		}
		break;
/*
	ESC [ pnl; pnc; H
	ESC [ pnl; pnc; f

Position the cursor to line and column. If origin mode set, then the line
number is relative to the specified window. */

	case 'f':
	case 'H':
		line= ansiarg() - 1;	/* args are 1 - N; lines are 0 - N */
		column= ansiarg() - 1; 
		if (origin) {		/* if relative to margins */
			line += top;	/* internal absolute, */
			if (line > bot) line= bot;

		} else if (line > screen.lines - 1) line= screen.lines - 1;

		if (column >= screen.cols) column= screen.cols - 1;
		break;

/*
	ESC [ s			remember cursor position
*/
	case 's':
		saved_line= line;
		saved_col= column;
		break;

/*
	ESC [ u			restore cursor position
*/
	case 'u':
		line= saved_line;
		column= saved_col;
		break;

/*
	ESC [ pn; pn; ... h	set modes
	ESC [ pn; pn; ... l	clear modes

Set or reset modes. */

	case 'h':
		n= 1;
		goto setmode;
	case 'l':
		n= 0;
setmode:;	while (areargs()) {
			switch (ansiarg()) {
				case 4: insmode= n; break;
				case 7: wrap= n; break;
				case 6: origin= n; break;
			}
		}
		break;

/* 
	ESC [ pn L	Insert Lines

Insert pn blank lines at the current line, scrolling the current line
and others below it down. */

	case 'L':
		for (i= ansiarg(); i--;) {
			scrolldn(line,bot,physflg);
		}
		break;

/*
	ESC [ pn M	Delete Lines

Delete pn lines at the cursor line. */

	case 'M':
		for (i= ansiarg(); i--;) {
			scrollup(line,bot,physflg);
		}
		break;
				
/*
	ESC [ pn K

Erase in line. 0 is to end of line, 1 to beginning of line, 2 
the entire line. */

	case 'K':
		if (areargs()) n= ansiarg(); else n= 0;

		if (n == 0) {
			clrline(line,column);

		} else if (n == 1) {
			i= column;
			column= 0;
			if (physflg) p_pos(line,column);
			while (i--) do_ascii(' ');

		} else if (n == 2) {
			clrline(line,0);
		}
		break;

/*
	ESC [ pn J

Clear in screen. 0 == to end of screen, 1 == to beginning of screen,
2 == entire screen, and home the cursor. If origin mode, then the "screen"
is the set margins. */

	case 'J':
		if (areargs()) n= ansiarg(); else n= 0;

		if (n == 0) {
			l= screen.lines - 1;	/* limits are physical */
			if (origin) l= bot;	/* unless origin mode */
			clrline(line,column);
			for (i= line + 1; i <= l; i++) {
				clrline(i,0);	/* then all lines below it */
			}

		} else if (n == 1) {
			l= 0; 			/* physical limit */
			if (origin) l= top;	/* but if origin mode logical */
			while (l < line) {
				clrline(l,0);	/* clear all lines, top */
				++l;		/* down to current line */
			}
			i= column;		/* beginning of current */
			column= 0;		/* line */
			if (physflg) p_pos(line,column);
			while (i--) do_ascii(' ');

		} else if (n == 2) {
			i= 0;
			l= screen.lines - 1;
			if (origin) {
				i= top;
				l= bot;
			}
			while (i <= l) clrline(i++,0);
			line= column= 0;	/* IBM ANSI kludge */
		}
		break;
/*
	ESC [ pn P

Delete pn characters at the cursor. */

	case 'P':
		for (i= ansiarg(); i--;) {	/* delete from virt. screen, */
			delchar(line,column);
		}
		paint(line,line);		/* repaint line */
		break;

	default: 
		break;

	}
}

/* Normal, printable character. Print it and update the cursor position. */

static do_ascii(c)
char c;
{
	(lptr[line + deforg] + column)-> character= c;
	(lptr[line + deforg] + column)-> attribute= screen.attribute;

	if (wrap) {				/* if wrap enabled, */
		if (physflg) p_char(c);		/* send character regardless */
		if (++column >= screen.cols) {	/* if it wraps, */
			column= 0;		/* column 0 */
			if (++line > bot) {
				line= bot;
				scrollup(top,bot,physflg);
				p_pos(line,column);
			}
		}

	} else {				/* do not autoscroll, */
/*		if ((line == screen.lines - 1) && (column == (screen.cols - 1)))
			return;		*/	/* dont write in last cell */

		if (physflg) p_char(c);
		if (++column >= screen.cols) {	/* if it wraps, */
			column= screen.cols - 1;/* backup */
		}
	}
}

/* Execute a linefeed. */

static dolf() {

	if (++line > bot) {
		line= bot;
		scrollup(top,bot,physflg);
	}
	backfill(' ',screen.def_attribute);
}

/* Execute a backspace. */

static dobs() {

	if ((column == 0) && (line == 0)) return;
	if (--column < 0) {
		column= screen.cols - 1;
		--line;
	}
}
/* Return a numeric arg, or 1 if none. If the arg is a string index,
return 1. */

static ansiarg() {

	if (argcnt >= argptr) return(1);
	return(args[argcnt++]);
}
/* Return true if there are any args. */

static areargs() {

	return (argcnt < argptr);
}
/* ........................................................................
...........................................................................
........................................................................ */

/* Clear a line, starting at line,column to the end of the line. */

static clrline(l,c)
int l,c;
{
	if (physflg) {
		p_pos(l,c);
		p_clreol();
	}
	clrln(l + deforg,c,screen.cols,' ',screen.def_attribute);
}
/* Repaint the screen, from line 'first' to line 'last', inclusively. Treat
0xff lines the same as null ones. */

paint(first,last)
int first,last;
{
int n;
char c;
struct _cell far *p;
int curlin;

	physflg= (lporg == deforg);			/* should we do physical output */

	while (first < screen.lines) {			/* (stay within bounds) */
		p_pos(first,0);				/* put cursor there, */
		p= lptr[first + lporg];			/* ptr to line */
		n= screen.cols;				/* screen width */
		if (! physflg) {			/* if not physical screen */
			p_chara('-',screen.def_attribute); /* indicate this */
			n= itoa(deforg - lporg + last - first);/* display line number */
			while (n++ < 4) p_chara(' ',screen.def_attribute);
			n= screen.cols - 5;
		}
		if (p-> character != 0xff) {		/* if line has been used */
			while (p-> character && n--) {
				p_chara(p-> character,p-> attribute); /* paint one line */
				++p;
			}
		}
		if (n) p_clreol();			/* clear end of line, */
		++first;
	} while (first <= last);

	p_pos(line,column);
}

/* Display a decimal number, return its length */

static itoa(n)
int n;
{
int length;

	if (n < 10) {
		p_chara(n + '0',screen.def_attribute);
		return(1);
	}
	length= itoa(n / 10);
	itoa(n % 10);
	return(length + 1);
}

/* Scroll the screen down one line, and clear the top line. */

static scrolldn(h,l,flag)
int h,l,flag;
{
char *s;

	if (flag) p_scrolldn(h,l);
	if (h > 0) h += deforg;		/* offset top line if not the edge */
	l += deforg;
	image_dn(h,l);	 		/* scroll */
	clrln(h,0,screen.cols,' ',screen.def_attribute); /* clear top line */
}

/* Scroll the logical screen up, between the two specified lines 
inclusively, and clear the bottom. Scroll the physical screen only
if the flag is set. */

static scrollup(h,l,flag)
int h,l,flag;
{
char *s,c;

	if (flag) p_scrollup(h,l);
	if (h > 0) h += deforg;
	l += deforg;
	image_up(h,l);
	clrln(l,0,screen.cols,' ',screen.def_attribute);
}
/* Delete a character at line and column from the virtual screen. */

static delchar(l,c)
int l,c;
{
struct _cell far *p;

	p= lptr[l + deforg];			/* ptr to correct line */
	while (c < screen.cols - 1) {		/* copy cells left */
		p[c].character= p[c + 1].character; /* stop if we copy a null */
		p[c].attribute= p[c + 1].attribute;
		if (! p[c].character) return;
		++c;
	}
	p[screen.cols - 1].character= '\0';	/* last cell is clear */
}

/* Insert a character at line and column. Characters get scrolled off
the end of the line. */

static inschar(l,c,d)
int l,c;
char d;
{
int i;
struct _cell far *p;

	p= lptr[l + deforg];
	for (i= screen.cols - 2; i <= c; i--) {	/* copy cells right, */
		p[i + 1].character= p[i].character;
		p[i + 1].attribute= p[i].attribute;
	}
	p[c].character= d;			/* write new char */
	p[c].attribute= screen.attribute;
}

