#ifndef lint
static char *sccsid = "@(#)bbcplot.c	1.8 (UKC) 28/4/87";
#endif  lint
#include <stdio.h>
#include <sgtty.h>	/* for TIOCGETP */
#include <signal.h>	/* for SIGINT etc */
#include <sys/types.h>	/* for sys/stat.h */
#include <sys/stat.h>	/* for struct stbuf etc*/
#include "bbcplot.h"

/* 
 * Library of primitive graphics functions for BBC micro with
 * UKC terminal emulator ROM.
 * Translated from dcw's original pascal
 */

/*
 *	bbcodd reflects whether this is the first byte of a pair to be
 *	massaged into three characters for transmission.
 */

static int bbcodd = 1;

/* If the 2 bytes of data are 'a' and 'b' then the bits will be sent as
 * follows:
 *
 *    a7 a6 a5 a4 a3 a2
 *       a1 a0 b7 b6 b5
 *       b4 b3 b2 b1 b0
 *    
 * These are added onto a base of 0x30 - in case any problem is experienced
 * with sending spaces.
 *
 * gewt - (UKC)
 */

/*
 *	bbcbyte: transmit a byte to be printed to the bbc;
 *	packs 2 bytes into 3 for transmission
 *	As this function takes about a third of dbbc's time, it has been
 *	optimised for the VAX-11 C compiler, hence some strange constructs.
 */

bbcbyte(c)
register unsigned char c;
{
	static unsigned char bbcbuff;

	if (bbcodd) {
		bbcbuff = c;
		bbcodd = 0;
		return;
	}
	putchar( (bbcbuff + (48<<2)) >> 2 );
	putchar( ( ( (bbcbuff&3)<<3 ) | (c>>5) ) + 48 );
	putchar( (c&31) + 48 );
	bbcodd=1;
}

bbcword(x)
register short x;
{
	bbcbyte((unsigned char) (x & 0377));
	bbcbyte((unsigned char) ( (x & (0377<<8)) >> 8 ));
}

/*
 *	Flush all pending commands to the terminal
 */
bbcflush()
{
	if (!bbcodd) bbcbyte(0);
	(void) fflush(stdout);
}

bbcplot(k,x,y)
char k;
short x,y;
{
	if (k == (DRAWF|POINT|ABS)) {
		/* point plotting laziness: circles etc overstrike points due to
		 * bbc's coarse resolution. Don't plot if it is the same as the
		 * last point.
		 */
		static int lastx = -1, lasty = -1;	/* impossible values */
		int realx, realy;
		realx = x/HOR; realy = y/VERT;
		if (realx == lastx && realy == lasty) return;
		lastx = realx; lasty = realy;
	}
	bbcbyte(25);
	bbcbyte(k);
	bbcword(x);
	bbcword(y);
	/* delay for triangle fill */
}

bbcmode(m)
char m;
{
	bbcbyte(22);
	bbcbyte(m);
}

/*	*** Notes on setting states of terminal, bbc etc. ***
 *	
 *	We maintain an integer "bbcstate" which reflects the current state
 *	of the terminal: cbreak mode, mesg n, in graphics mode etc.
 *	Setting and clearing of these bits is to be done such that, if an
 *	interrupt occurs, we can always act sensibly in the light of these bits.
 *	This implies that remembered values such as previous tty modes must be 
 *	filled in correctly before the bit is set (so that it is not set back to
 *	a random state). Since the action and the bit-twiddling cannot be done
 *	atomically, do them in such an order that something sensible will happen
 *	if an interrupt occurs between them.
 *
 *	So that the intelligencia can do "dbbc file > file.bbc", do actions on
 *	file descriptor 1, not /dev/tty, and don't complain if tty ioctls fail.
 *
 *	Normal states of the bbc are:
 *	Boring mode: Text mode, cooked etc, as it was when we were invoked
 *	Active mode: Graphics state, cbreak, -echo, messages off,
 *			text at graphics cursor, hence cursor off
 */

static int bbcstate = 0;	/* All normal */
#define GRAPHICS 1	/* BBC after a char 1 */
#define TTYICK	 2	/* CBREAK etc in force, messages off */

struct sgttyb oldsgtty, newsgtty;
struct stat stbuf;
int abortjob();		/* declare for use in signal() call */

bbcopen()
{
	(void) signal(SIGINT, abortjob);
	(void) signal(SIGHUP, abortjob);
	if ( (bbcstate&TTYICK) == 0) {
		(void) ioctl(1, TIOCGETP, &oldsgtty);
		(void) fstat(1, &stbuf);	/* get original tty permissions for later restoration */
		newsgtty = oldsgtty;		/* copies elements of each */
		bbcstate |= TTYICK;
		newsgtty.sg_flags |= CBREAK;
		newsgtty.sg_flags &= ~ECHO;
		(void) ioctl(1, TIOCSETP, &newsgtty);	/* set new tty mode */
#ifdef BSD4_2
		fchmod(1, stbuf.st_mode&~022);	/* Turn off write permission to the world on the tty */
#endif
	}

	if ( (bbcstate&GRAPHICS) == 0) {
		bbcstate |= GRAPHICS;
		putchar(1);
		putchar('\n');
		bbcodd=1;
		bbcbyte(5);	/* Text at graphics cursor; cursor off */
	}
}

/*
 *	Wait for a keypress
 *
 *	In graphics mode (in which we must be) it appears that the only keys
 *	which have any effect are space and the escape key.
 *	Empirically, Space returns 32, and Escape returns 10 (?!) in ch.
 *	Pressing <escape> also causes the BBC to leave graphics mode.
 */

bbcwait()
{
	char ch;

	bbcbyte(4);
	bbcflush();
	(void) read (1, &ch ,1);
	if (ch==10) {
		bbcstate &= ~GRAPHICS;
		bbcclose();
		exit(1);
	}
	bbcbyte(5);
	bbcflush();
	(void) fflush(stdout);		/* Let them know that we heard */
}

bbcclose()
{
	if (bbcstate&GRAPHICS) {
		bbcbyte(4); /* print text at text cursor (cancels bbcbyte(5)) */
		if (bbcodd)
			bbcbyte(27);
		bbcbyte(27); /* code 27 reverts to normal mode */
		bbcbyte(13);
		putchar('\n');
		(void) fflush(stdout);
		bbcstate &= ~GRAPHICS;
	}
	if (bbcstate&TTYICK) {
		(void) ioctl(1, TIOCSETP, &oldsgtty);
#ifdef BSD4_2
		fchmod(1, stbuf.st_mode);
#endif
		bbcstate &= ~TTYICK;
	}
}

bbcdchar(ch,g)
char ch;
grid g;
{
	register int i;

	bbcbyte(23);
	bbcbyte(ch);
	for (i=0;i<8;i++)
		bbcbyte(g[i]);
}

bbcgcol(style, colour)
unsigned int style, colour;
{
	bbcbyte(18);
	bbcbyte(style);
	bbcbyte(colour);
}

/*
 *	Unsuccessful completion; try to bail out and
 *	not to leave the terminal in yillruction mode
 *	Try to retrieve as much as possible from the output buffers
 *	without messing up the 2->3 byte packing.
 */
abortjob()
{
	char buf[BUFSIZ];
	register n, i;

	n = retrievebuf(buf, stdout);
	for (i=0; i<n%3; i++)
		putchar(buf[i]);
	bbcodd = 1;	/* kill the first byte of a pair if any */
	raiseeyebrows();
	bbcclose();
	exit(1);
}

/*
 *	retrievebuf: flush a stdio buffer into an area of memory
 *	returns the number of byte un-written. Buf should be at least BUFSIZ
 *	for obvious reasons. If the you have set the buffer yourself, you will
 *	know its size.
 *
 *	A munged version of fflush() from the VAX library.
 */

retrievebuf(buf, iop)
char *buf;
register struct _iobuf *iop;
{
#ifdef BSD4_2
	register char *base;
	register n;

	if ((iop->_flag&(_IONBF|_IOWRT))==_IOWRT
	 && (base=iop->_base)!=NULL && (n=iop->_ptr-base)>0) {
		iop->_ptr = base;
		iop->_cnt = (iop->_flag&(_IOLBF|_IONBF)) ? 0 : iop->_bufsiz;
		bcopy(base, buf, n);
		return(n);
	}
#endif
	return(0);
}

/* complete any half-baked bbc commands so that we have the beeb's full
 * attention for what we are about to say */

raiseeyebrows()
{
	register int i;

	for (i=0; i<8; i++) bbcbyte(0); /* kills a definechar command (9 bytes) */
}

error(string)
char *string;
{
	if (bbcstate&GRAPHICS) {
		bbcbyte(7);
		bbcwait();
	}
	bbcclose();
	fprintf(stderr, "dbbc: %s.\n", string);
	exit(1);
}
