#ifndef lint
static char *sccsid = "@(#)term.c	1.11 (UKC) 13/2/89";
#endif  lint

/*
 * term.c of dterm
 *
 * Functions for driving the terminal emulator directly
 *
 * It is believed to be a portable module, but I haven't actually tried it yet.
 *
 * All positions are bitwise, with (0, 0) indicating the top left corner.
 * TERM's escape sequences use (1, 1) to mean top left corner - this is
 * allowed for at the very lowest level when doing absolute motions.
 *
 *	term_init()	initialise this package and the ST.
 *	term_deinit()	restore terminal state for exit
 *	term_dot(x,y)	set a point at bit position (x,y)
 *
 *	set_bitmap(m)	Determine whether we are in bitmap mode or not (1 or 0)
 *	set_cursor(m)	Determine whether the cursor os displayed or not
 *	set_bg(m)	Determine the background colour.  black = 0, white = 1
 *	set_font(tfp)	Set the current font to that described by the referenced
 *			font structure.
 *	set_pos(x, y)	Set the current cursor position (bitwise) to be (x, y).
 *
 *	term_clear()	Clear the screen
 *	term_char(x,y,c)Set a character with its top left corner at (x, y).
 *
 *	term_save()	remember the current state of term
 *	term_restore()	Set back to the state we were in at last term_save()
 *
 * Read-only variables, exported from term.c and term_font.c:
 *
 *	int term_width, term_height
 *		size of current window in pixels
 *
 *	int term_hpos, term_vpos
 *		current position on the screen. (0,0) is top left corner
 *
 *	int term_curfont;
 *			number of current font (see set_font())
 *
 *	int term_version
 *			version of term that they are running (values in term.h)
 *
 * Writable variables
 *
 *	int term_cslop
 *		How many pixels characters are allowed to be mispositioned by
 *		horizontally, if it will speed output.  Default = 0.
 *
 *	Conditional compilation flags:
 *	OPTCOUNT	generate statistics on the success of optimisations
 *	TERM6_7		workaround features of term version 6.7
 *			- when you turn bitmap mode off, vertical position is
 *			  adjusted upwards to the next full screen line.
 *			  things only go on the top line if they are at col=1.
 *
 *	TERM8_1		Use character bitblt operator stuff
 *
 *	TERM8_4		Use bitwise motions in character mode
 */

#define TERM8_1
#define TERM8_4

/*
 *	Data for driving the terminal
 */

#include <stdio.h>
#include <sgtty.h>
#include "term.h"
#include "term_font.h"

/*
 *	Characteristics of the terminal
 */

/* Should make these ints and resize them to the window by interrogating the
 * terminal emulator
 */
int term_width	= TERM_WIDTH;	/* width of screen in pixels */
int term_height	= TERM_HEIGHT;	/* height in pixels */

/*
 * Current font, corresponding to the STs ideas of font numbering,
 * viz: (0,1,2,3) = (8x10,6x8,8x16,7x10).
 * Initialised during initial interrogation of TERM in term_init()
 * or at first call of setfont().
 */
int term_curfont = 0; /* Initialised to a possible value for rubustnitude. */

/*
 * Two quick-access variables for term_char, containing
 * term_font[term_curfont].tf_{width,height}, which must be updated
 * when term_curfont changes.
 */
static int cur_width, cur_height;

/*
 *	The following two are not part of the documented interface to term.c,
 *	but are used by dterm.c for a per-character speed up.
 */
uchar *cur_hoff33;		/* pointer to entry on tf_hoffset which can
				 * be indexed by the raw character code */
int cur_voff;			/* tf_voffset of current font */

/* Where the cursor actually is on the screen at the moment, in cursor coords */
int term_hpos = -1, term_vpos = -1;	/* impossible values */

/* how many pixels characters are allowed to be mis-positioned by
 * if it will speed output */
int term_cslop = 0;

/* What version of term are they running? (values in term.h) */
int term_version = TERM_VUNKNOWN;

static struct sgttyb oldsgtty;	/* original terminal modes */
static int ttyick = 0;		/* have we buggered the tty modes? */

/* somewhere to remember the old terminal attributes for restoration on exit */
static char old_version, old_bg, old_font;

/* what colour is term's background at the present moment in time? */
static int dev_bg = -1;		/* what colour is the terminal's background? */
static int dev_bitmap = 0;	/* is the terminal in bitmap mode? */


term_init()
{
	static struct sgttyb newsgtty;	/* modified terminal modes */

	if (ioctl(1, TIOCGETP, &oldsgtty) == -1) {
		/* ioctl fails.  Could be outputting into a file. */
	} else {
		/* set terminal mode to CBREAK and -ECHO */
		newsgtty = oldsgtty;
		newsgtty.sg_flags |= CBREAK;
		newsgtty.sg_flags &= ~ECHO;
		ttyick = 1;
		ioctl(1, TIOCSETP, &newsgtty);	/* set terminal modes, flushing pending input */
	}

	/*
	 * Turn text cursor off early so that they can't see us piddle-arsing
	 * around to work out the version of TERM they are using.
	 */
	set_cursor(0);		/* turn text cursor off */

	/*
	 *	Clear the layer, which moves cursor to (1,1)
	 */
	putchar('\f');

	/* Should enquire current font etc for reset later on */
	if (isatty(1)) {
		int row, col;
		char ch, c1, c2, c3;

		/*
		 * Work out term version
		 *
		 * 1. If we have bitwise motions from text mode, this is at
		 *    least 8.4.  Use bitmap motion to move at least one char
		 *    width and report.
		 * Otherwise assume 8.1.  There's probably a test for 6.7 if we
		 * want to, involving the way the cursor tries to align to a
		 * text line when changing from bitmap to text mode.
		 */
		fputs("\033[?8C", stdout);	/* Move (or not, if pre-8.4) */
		fputs("\033[6n", stdout);	/* Report cursor position */
		fflush(stdout);

		/* Read reply */
		if (scanf("\033[%d;%dR", &row, &col) != 2) goto out;

		if (col == 2) {
			term_version = TERM_V8_4;
		} else {
			term_version = TERM_V8_1;
		}

		/*
		 * Get terminal attributes for later restoration
		 * Bloody bleeding bloody pc started out with the first
		 * character of the reply being the program version number,
		 * but didn't stick with it!  Grrrrrrrrrrrrr.
		 * Put me through the hoop would you?
		 *
		 * 2nd char: background 0 = black, 1 = white.
		 * 3rd char: font, (0,1,2,3) = (8x10,6x8,8x16,7x10)
		 */
		fputs("\033Z", stdout);
		fflush(stdout);
		if (scanf("\033[?%c%c%cc", &c1, &c2, &c3) != 3)
			goto out;

		/* Remember the original attributes, but also note them
		 * as the current attributes.
		 */
		dev_bg       = old_bg   = c2 - '0';
		term_curfont = old_font = c3 - '0';
		set_cur_();		/* Update dependent variables */

		/* 
		 * Find out window size by a streshape-like trick
		 */
		set_bitmap(1);
		printf("\033[%dB\033[%dC", TERM_HEIGHT, TERM_WIDTH);

		/* Report cursor position */
		fputs("\033[6n", stdout);
		fflush(stdout);

		/* Read reply. Because TERM speaks with an origin of (1,1),
		 * the value returned by the bottom right corner is equal
		 * to the dimensions of the window.
		 */
		if (scanf("\033[%d;%dR", &row, &col) != 2) goto out;
		term_width  = col;
		term_height = row;

		term_vpos = -1;		/* force an absolute motion next */
	}
out:

	/* specify bitmap bitblt operator by turning bitmap mode off */
	fputs("\033[?32;8l", stdout);
	dev_bitmap = 0;

	/* specify character bitblt operator to "or" new characters in. */
	/* Only actually happens in term version 8.1 onward. */
	if (term_version >= TERM_V8_1) fputs("\033[?8m", stdout);
}

/*
 * Reset the terminal for exit.
 *
 * The first character term_deinit puts out must be an escape because it is
 * called in cases when there may be half-baked escape sequences in progress.
 * An escape inside an escape sequence cancels the incomplete one and actions
 * the new.
 */
term_deinit()
{
	set_bitmap(0);	/* clear bitmap mode if it was on */

	/* set text blit mode back to normal (only from TERM 8.1 onwards */
	if (term_version >= TERM_V8_1) fputs("\033[?4m", stdout);

	/* reset term's attributes (current font etc) */
	set_font(old_font);
	set_bg(old_bg);

	set_cursor(1);	/* turn text cursor back on */

	fflush(stdout);

	if (ttyick) ioctl(1, TIOCSETN, &oldsgtty);
}

/*
 * term_dot(h,v) - plot a point on the screen.
 *
 * Speed optimisation: we can set 16 adjacent bits with 3 characters if we
 * can spot them.  Point plotting normally happens in little clusters.
 * Strategy: when we get the first point of a cluster don't plot it
 * immediately, but prepare for the next pixel to be within 15 of it.
 * When either the next point is outside this little horizontal strip,
 * or we have to switch out of bitmap mode, plot the buffered stripe.
 */

static int pendingdots = 0;	/* overall width of buffered points in stripe */
static unsigned short stripe;	/* left-adjusted stripe to plot */
static int stripe_h, stripe_v;	/* x- and y-coord of left hand end of stripe */

static add_to_stripe();
static flush_stripe();

term_dot(h, v)
{
	static int lasth = 0, lastv = 0;

	/* Optimise out repeated plotting of the same point.
	 * This will miss one point if the first dot on a page falls at the
	 * same position as the last point on the previous page.  Big deal.
	 */
#ifdef OPTCOUNT
	cnt_drawdot++;
#endif
	if (h == lasth && v == lastv) {
#ifdef OPTCOUNT
		opt_drawdot++;
#endif
		return;
	}

	/* Clip to screen (common problem in dterm -u) */
	if (v >= term_height || h >= term_width) return;


	if (!pendingdots) {
		/* Initialise optimising stuff */
start_optim:	stripe = 0x8000;	/* just one bit */
		stripe_h = h;
		stripe_v = v;
		pendingdots = 1;
	} else {
		/* already optimising.  Can we carry on with this stripe? */
		if (v == lastv) {
			/* maybe... */
			if (h >= stripe_h && h < stripe_h+16) {
				/* In our current stripe. Super! */
				add_to_stripe(h);
			} else
			if (h >= stripe_h + pendingdots - 16 &&
			    h < stripe_h + pendingdots) {
				/* Need to slip out 16-bit window left
				 * As the points are going leftwards, go as far
				 * as pendingdots will allow
				 */
				int shift = 16 - pendingdots; /* how many bits to shift the window left by */

				stripe >>= shift;
				stripe_h -= shift;
				pendingdots += shift;
				add_to_stripe(h);
			} else {
				goto out_of_stripe;
			}
		} else {
out_of_stripe:		/* Outside our stripe. Display the old stripe and play
			 * the optimising game again.
			 */
			flush_stripe();
			goto start_optim;
		}
	}

	lasth = h; lastv = v;
}

/* add pixel at horizontal coord h to the stripe.  It is guaranteed to fall
 * within the stripe at the current setting of stripe_h
 */
static
add_to_stripe(h)
{
	int newpd;		/* new value of pd, if larger than old */

	stripe |= 0x8000 >> (h - stripe_h);
	newpd = h-stripe_h+1;
	if (newpd > pendingdots) pendingdots = newpd;
}

static
flush_stripe()
{
	pendingdots = 0;		/* do this first to prevent mutual recursion from set_pos */
	set_pos(stripe_h, stripe_v);
	set_bitmap(1);
	putchar('0' + ((stripe>>10) & 0x3f));
	putchar('0' + ((stripe>>5)  & 0x1f));
	putchar('0' + (stripe & 0x1f));
	term_hpos += 16;
}


/* Ensure we are in bitmap mode or not, as required */

set_bitmap(mode)
{
	if (mode == 0 && pendingdots) flush_stripe();

	if (dev_bitmap != mode) {
		fputs("\033[?32", stdout);
		putchar(mode ? 'h' : 'l');
		dev_bitmap = mode;
	}
}

/* Ensure the text cursor is on or not, as required */
/* 'l' turns the cursor on, 'h' turns it off.  great. */

static int dev_cursor = 1;	/* is the cursor on? */

set_cursor(mode)
{
	if (dev_cursor != mode) {
		fputs("\033[?31", stdout);
		putchar(mode ? 'l' : 'h');
		dev_cursor = mode;
	}
}

/*
 * Set backgound colour for screen.  Black background = 0, white bg = 1.
 */

set_bg(bg)
{
	if (dev_bg != bg) {
		printf("\033[?5%c", bg ? 'h' : 'l');
		dev_bg = bg;
	}
}

/*
 * Select current displayed font.
 * font numbers (0,1,2,3) = (8x10,6x8,8x16,7x10)
 */
set_font(font)
int font;
{
	if (font != term_curfont) {

		register struct term_font *tfp = &term_font[font];

		set_bitmap(0);	/* changing font inside bitmap screws char positioning */
		printf("\033[%dm", term_font[font].tf_select);
		term_curfont = font;
		set_cur_();
	}
}

/*
 * Update the fast copies of bits of the current font structure
 */
static set_cur_()
{
	register struct term_font *tfp = &term_font[term_curfont];

	cur_width = tfp->tf_width;
	cur_height = tfp->tf_height;
	cur_hoff33 = tfp->tf_hoffset - 33;
	cur_voff = tfp->tf_voffset;
}

/*
 *	Clear the screen
 */
term_clear()
{
	/* might be able to use ^L, but the TERM document only says that
	 * "all escape sequences will work in bitmap mode" */
	fputs("\033[2J", stdout);
}

/*
 * 	Set a character at bitmap position (h,v).  A separate routine for
 *	characters cos there are some optimisations you can do with chars
 *	(slop) which you can't do with other motions.
 */

static setting_char = 0;	/* Can we apply character-oriented optimisations? */

term_char(h, v, c)
char c;
{
	setting_char = 1;

#ifdef TERM6_7
	/* when turning bitmap mode off, vpos may be incremented to align
	 * with the character grid.  It may be incremented by 0..7, and only
	 * a bitmap row of 1 will leave you on the top line of the screen.
	 */
	h = h - cur_height / 2;
	if (h < 1) h = 1;
#endif

	/* Clip to screen (common problem in dterm -u) */
	if (v >= term_height || h >= term_width) return;

	set_pos(h, v);

	set_bitmap(0);
	putchar(c);

	/* remember that the print head advances */
	term_hpos += cur_width;

	setting_char = 0;
}

/*
 *	Move to (bitwise) position on the screen.
 *	origin == (0, 0) == top left corner;
 *	Note that when addressing TERM, origin is (1, 1)
 *
 *	Lazy evaluation of cursor motion is resolved here.
 */

set_pos(h, v)
{
	/* are we already in the right place? */
#ifdef OPTCOUNT
	cnt_set_pos++;
#endif
	if (h == term_hpos && v == term_vpos) {
#ifdef OPTCOUNT
		opt_set_pos++;
#endif
		return;
	}
		
	if (pendingdots) flush_stripe();

	/* Can we just do a horizontal motion? (common for text)
	 *
	 * This is where output size reduction is at its most effective, since
	 * running text requires an average of 15 characters of escape codes to
	 * do a bitwise motion.
	 */
	if (v == term_vpos) {
		register int motion;

		/* are we close enough to avoid doing a motion? */
		if (term_cslop && setting_char) {
			/* How much would we be out by if we set the char right here
			 * without a motion? */
			register int misplacement;

			misplacement = h - term_hpos;
			if (misplacement < 0) misplacement = -misplacement;

			if (misplacement <= term_cslop) return;
		}

		motion = h - term_hpos;

		/* Term version 8.4 has bitwise motions in character mode with
		 * <esc>[?1;2H <esc>[?34C etc
		 */
		if (term_version >= TERM_V8_4) {
			if (dev_bitmap == 0) {
				/* use bitwise motion in char mode */
				if (motion > 0)
					if (motion == 1)  fputs("\033[?C", stdout);
					else		  printf("\033[?%dC", motion);
				else 	if (motion == -1) fputs("\033[?D", stdout);
					else		  printf("\033[?%dD", -motion);
			} else {
				/* already in bitmap mode */
				if (motion > 0)
					if (motion == 1)  fputs("\033[C", stdout);
					else		  printf("\033[%dC", motion);
				else 	if (motion == -1) putchar('\b');
					else		  printf("\033[%dD", -motion);
			}
		} else {
			/* need to be in bitmap mode for bit-aligned motion */
			set_bitmap(1);

			if (motion > 0)
				if (motion == 1)  fputs("\033[C", stdout);
				else		  printf("\033[%dC", motion);
			else 	if (motion == -1) putchar('\b');
				else		  printf("\033[%dD", -motion);
		}

		term_hpos = h;
	} else {
		/* Contrary to the TERM manual of May 1987, the order of the arguments
		 * to "Move cursor" is <row>;<col>
		 *
		 * Absolute TERM coordinates and our internal coords have
		 * origins of (1,1) and (0,0) respectively, so need to
		 * compensate when using absolute motions
		 */

		if (term_version >= TERM_V8_4) {
			if (dev_bitmap == 0) {
				printf("\033[?%d;%dH", v+1, h+1);
			} else {
				printf("\033[%d;%dH", v+1, h+1);
			}
		} else {
			/* need to be in bitmap mode for bit-aligned motion */
			set_bitmap(1);
			printf("\033[%d;%dH", v+1, h+1);
		}

		term_hpos = h;  term_vpos = v;
	}
}

static int sav_bitmap, sav_cursor;
/*
 *	Save term state for later restoration
 */
term_save()
{
	sav_bitmap = dev_bitmap;
	sav_cursor = dev_cursor;
}

/*
 *	Restore state of terminal after suspension.
 */
term_restore()
{
	set_bitmap(sav_bitmap);
	set_cursor(sav_cursor);
	term_hpos = term_vpos = -1;	/* impossible values to force absolute move */
}
