/*
 * screen.pc:
 *	IBM PC screen handling
 *
 * Copyright (c) 1989 University of Toronto. All rights reserved.
 * Anyone may use or copy this software, except that it may not be
 * sold for profit, that this copyright notice remain intact, and that
 * credit is given where it is due. The University of Toronto and the
 * author make no warranty and accept no liability for this software.
 */
static char rcsid[] = "$Header: screen.pc,v 1.8 89/06/19 17:16:54 pkern Exp $";

#include <dos.h>
#include "cterm.h"
#include "pc.h"

#ifdef BGI
#include <conio.h>
#include <dir.h>
#include <graphics.h>
#endif

extern uchar interlace;

static union REGS r;
static struct SREGS sr;
static struct REGPACK rp;

static int o_scrn[WIDE * ROWS];	/* screen memory storage */
static int o_xy=0;		/* cursor position memory */
static int *scrn;		/* screen position memory */

static unsigned int lbuf[WIDE+1];

static int ncols=0, vmode=-1, pgnum=0;	/* see init_scr() */
static int o_ncols=0;
static unsigned int vaddr=0xb000;

/* cursor types */
static unsigned int crsr_blk=0x000c, crsr_ul=0x0b0c;

#define sPOS(a, b)	((b)*ncols+(a))
static int *scp;	/* general purpose screen pointer */

/* macros for frequently used code */
#define crtint		int86(CRT_INT, &r, &r)

#define BLANK	0x20
#define NORM	0x07

#define GO_HOME		r.h.ah=2; r.h.bh=pgnum; r.x.dx=0; crtint;
#define GO_XY(a,b)	\
	r.h.ah=2; r.h.bh=pgnum;	r.h.dh=(b); r.h.dl=(a); crtint;

#define CX_POSN		*((char far *)(0x00450+(pgnum<<1)))
#define CY_POSN		*((char far *)(0x00451+(pgnum<<1)))
#define CURS_POS	*((int far *)(0x00450+(pgnum<<1)))

#define CLEAN(a)	\
	r.x.ax=0x0920; r.x.bx=(pgnum<<8)|c_norm; r.x.cx=(a); crtint;

static uchar xattr=NORM;	/* attribute storage */
static uchar rattr=0, o_rattr=0;

uchar c_norm=NORM, c_rev=NORM, c_bold=NORM, c_ul=1, g_type=0;

/*
 * init_scr:
 *	try to figure out where the display is
 *	and set our internal variables to reflect it.
 */
init_scr()
{
#ifdef BGI
	if (gfx_driver == DETECT) {
		detectgraph(&gfx_driver, &gfx_mode);
		switch (gfx_driver) {
		case CGA: case MCGA:
			txt_mode = BW80;
			txt_addr = 0xb800;
			break;
		case EGA: case EGA64: case EGAMONO:
			txt_mode = BW80;
			txt_addr = 0xa000;
			break;
		default:
			txt_mode = MONO;
			txt_addr = 0xb000;
			c_ul = 1;
		}
	}
	textmode(BW80);
#else
	if (vmode < 0)
		{ r.h.al = txt_mode; vaddr = txt_addr; }
	else
		r.h.al = vmode;
	r.h.ah = 0; crtint;

	scrn = (int far *) MK_FP(vaddr, 0);
#endif

	r.h.ah = 0x0f;	/* get video mode */
	crtint;
	ncols = r.h.ah;	/* num of cols */
	vmode = r.h.al;	/* video mode */
	pgnum = r.h.bh;	/* page number */

	r.h.ah = 0x03;	/* get cursor mode */
	r.h.bh = pgnum;
	crtint;
	crsr_blk = (r.h.cl < 7) ? 7 : r.h.cl;
	crsr_ul = ((r.h.cl-1) << 8) | r.h.cl;

	segread(&sr);

	attribs(rattr);

	return(vmode == txt_mode);
}

reset_scr()
{
#ifdef BGI
	if (gfx_driver > 0) {
		restorecrtmode();
		closegraph();
	}
	gfx_driver = DETECT;
#endif
}

curs_xy(x, y)
uchar x, y;
{
	GO_XY(x-1, y-1)
}

xypos(xp, yp)
uchar *xp, *yp;
{
/*	r.x.dx = CURS_POS; */

	r.h.ah = 3;
	r.h.bh = pgnum;
	crtint;

	*xp = r.h.dl+1;
	*yp = r.h.dh+1;
}

columns(n)
int n;
{
	if (!avm_mode)	/* can't do 132 columns */
		return(ncols);

	if (n > 80)
		{ vmode = avm_mode; vaddr = avm_addr; }
	else
		{ vmode = txt_mode; vaddr = txt_addr; }

	init_scr();

	if (o_ncols)
		o_xy = o_ncols = 0;

	return(ncols);
}

bkgnd(sw)
int sw;
{
	/* nothing yet */
}

/* scroll up n lines between min_y and max_y */
scrl_up(n, min_y, max_y, x, y)
uchar n, min_y, max_y, x, y;
{
	if (n > max_y-min_y+1)
		n = max_y-min_y+1;

	r.h.ah = 6; r.h.al = n; r.h.bh = c_norm;
	r.h.cl = 0; r.h.ch = min_y-1;
	r.h.dl = ncols-1; r.h.dh = max_y-1;
	crtint;
}

/* scroll down n lines between min_y and max_y */
scrl_down(n, min_y, max_y, x, y)
uchar n, min_y, max_y, x, y;
{
	if (n > max_y-min_y+1)
		n = max_y-min_y+1;

	r.h.ah = 7; r.h.al = n; r.h.bh = c_norm;
	r.h.cl = 0; r.h.ch = min_y-1;
	r.h.dl = ncols-1; r.h.dh = max_y-1;
	crtint;
}


/* eol : erase to end of line */
eol_erase(x, y)  
uchar x, y;
{
	if (x > ncols) return;
	CLEAN(ncols-(x-1))
}

/*
 * bol : erase from beginning of line to cursor pos.
 * ie. goto head of line, erase chars, return to original pos
 */
bol_erase(x, y)
uchar x, y;
{
	GO_XY(0, y-1)
	CLEAN(x)
	GO_XY(x-1, y-1)
}

/* eos : erase to end of line and to end of screen */
eos_erase(x, y)
uchar x, y;
{
	CLEAN((ROWS-y)*ncols + ncols-x+1)
}

/*
 * bos : erase from beginning of screen (home) to cursor pos.
 */
bos_erase(x, y)
uchar x, y;
{
	GO_HOME
	CLEAN(ncols*(y-1)+x)
	GO_XY(x-1, y-1)
}

/* goto head of line, erase line, return to original pos */
line_erase(x, y)
uchar x, y;
{
	GO_XY(0, y-1)
	CLEAN(ncols)
	GO_XY(x-1, y-1)
}

/* erase entire screen (not including status line) */
all_erase(x, y)
uchar x, y;
{
	if (interlace) return;
	GO_HOME
	CLEAN(ncols*ROWS)
	GO_XY(x-1, y-1)
}

/*
 * insert line(s) :
 * scroll down n lines, goto head of original line, erase empty space
 */
line_ins(n, x, y, max_y)
uchar n, x, y, max_y;
{
	scrl_down(n, y, max_y, x, y);
	GO_XY(0, y-1)
}

/*
 * delete line(s) :
 * scroll up n lines, goto new empty line, erase lines,
 * return to head of original line
 */
line_del(n, x, y, max_y)
uchar n, x, y, max_y;
{
	scrl_up(n, y, max_y, x, y);
	GO_XY(0, y-1)
}

/*
 * delete char(s) :
 * ie. copy rest of line to the left, goto line-end, erase n chars
 * and return to original cursor pos
 */
char_del(n, x, y)
uchar n, x, y;
{
	int *p;
	register uchar yn = y-1;

	if (x > ncols) return;
	if (x+n-1 > ncols) n = ncols-x+1;

#ifdef BGI
	movetext(x+n,y, ncols,y, x,y);
#else
	scp = &scrn[sPOS(x-1, yn)];
	p = &scrn[sPOS(x+n-1, yn)];
	movmem(p, scp, (ncols-(x+n-1)) * 2);

/*	scp = &scrn[sPOS(ncols-n, yn)];
	memset(scp, '\0', n * 2);	*/
#endif

	GO_XY(ncols-n, yn)
	CLEAN(n)
	GO_XY(x-1, y-1)
}

/*
 * insert char(s) :
 * ie. copy rest of line to right and clean out new space(s)
 */
char_ins(n, x, y)
uchar n, x, y;
{
	int *p;
	register uchar yn = y-1;

	if (x > ncols) return;
	if (x+n-1 > ncols) n = ncols-x+1;

#ifdef BGI
	movetext(x,y, ncols-n,y, x+n,y);
#else
	scp = &scrn[sPOS(x-1, yn)];
	p = &scrn[sPOS(x+n-1, yn)];
	movmem(scp, p, (ncols-(x+n-1)) * 2);

/*	scp = &scrn[sPOS(x-1, yn)];
	memset(scp, '\0', n * 2);	*/
#endif

	GO_XY(x-1, yn)
	CLEAN(n)
	GO_XY(x-1, y-1)
}

/*
 * set attributes
 */
attribs(attr)
uchar attr;
{
	rattr = attr;
	xattr = c_norm;
	if (attr & AT_UL)	xattr =  c_ul;
	if (attr & AT_BOLD)	xattr |= 0x08;
	if (attr & AT_BLINK)	xattr |= 0x80;
	if (attr & AT_REV)	xattr = (xattr & 0x88) | (c_rev << 4);
}

/*
 * screen alignment test :
 * home cursor and write a screen of E's in normal attrib mode.
 * previous attribs are not forgotten.
 */
e_screen()
{
	GO_HOME
	r.h.ah = 0x09;
	r.h.al = 'E';
	r.h.bh = pgnum;
	r.h.bl = c_norm;
	r.x.cx = ncols * ROWS;
	crtint;	/* screen of E's */
}

/* basic single-char output */
burpc(c, x, y)
int c, x, y;
{
	r.h.ah = 0x09;
	r.h.al = c;
	r.h.bh = pgnum;
	r.h.bl = xattr;
	r.x.cx = 1;
	crtint;
	GO_XY(x, y-1)
}

/* sound bell/horn/whistle/whatever */
beep(x, y)
int x, y;
{
/*
	r.x.ax = 0x0e07;
	r.x.bx = (pgnum << 8);
	crtint;
 */
/*	honk(0x533, 64);	/* normal 1000 Hz bell */
/* mildly fancy
	honk(0x333, 32);
	honk(0x533, 32);
	honk(0x733, 32);
 */
#ifdef PLAY
/* getting fancier ...	*/
	play("g3.24,d3.24,g3.24,d3.24,g3.24");
#else
/* default */
	honk(2915, 64);	/* middle C (approx. 440 Hz) */
#endif
}

/* insert mode burpc() */
/* move line right, deposit char */
insurpc(c, x, y)
int c, x, y;
{
	char_ins(1, x, y);
	burpc(c, x, y);
}

/* output string at x,y with attr */
burps(s, x, y, at)
char *s;
uchar x, y, at;
{
	int c;
	uchar xn = x, yn = y;

#ifdef BGI
	GO_XY(x-1, y-1)

	yn = xattr;
	attribs(at);
	c = xattr << 8;
	xattr = yn;
	
	if (!s[1]) {
		r.h.ah = 0x09; r.h.al = *s;
		r.h.bh = pgnum; r.h.bl = c >> 8;
		r.x.cx = 1; crtint;
		return;
	}

	xn = 0;
	while (*s)
		lbuf[xn++] = c | *s++;
	puttext(x, y, x+xn-1, y, lbuf);
#else
	c = xattr;
	attribs(at);

	GO_XY(xn-1, yn-1)
	while (*s) {
		r.h.ah = 0x09; r.h.al = *s++;
		r.h.bh = pgnum; r.h.bl = xattr;
		r.x.cx = 1; crtint;
		GO_XY(xn++, yn-1)
		if (xn == ncols) {
			xn = 1;
			if (yn < ROWS) yn++;
		}
	}
	GO_XY(x-1, y-1)
	xattr = c;
#endif /* BGI */
}

/* set cursor type */
curs_type(sw)
uchar sw;
{
	r.h.ah = 1;
	r.x.cx = (sw) ? crsr_blk : crsr_ul;
	if (sw & 0x80) r.h.ch = 0x20 | (r.h.ch & 0x1f);
	crtint;
}

/* save screen image & cursor position */
save_scr()
{
	register int n;
	register int *os, *s;

	if (interlace) return;

	r.h.ah = 3; r.h.bh = pgnum; crtint; o_xy = r.x.dx;

	n = ncols * ROWS;
#ifdef BGI
	gettext(1,1, ncols,ROWS, o_scrn);
#else
	s = scrn; os = o_scrn;
/*	while (n--) *os++ = *s++;	*/
	movmem(s, os, n * 2);
#endif
	o_ncols = ncols;
	o_rattr = rattr;
}

/* restore screen image & cursor position */
restore_scr()
{
	register int n;
	register int *os, *s;

	if (interlace) return;

	n = ncols * ROWS;
	GO_HOME
	CLEAN(n)

	if (o_ncols) {
#ifdef BGI
		puttext(1,1, ncols,ROWS, o_scrn);
#else
		s = scrn; os = o_scrn;
/*		while (n--) *s++ = *os++;	*/
		movmem(os, s, n * 2);
#endif
	}

	attribs(o_rattr); o_rattr = 0;
	r.h.ah = 2; r.h.bh = pgnum; r.x.dx = o_xy; crtint;
	o_ncols = 0;
}

/* save text in line lnum from o_scrn to buf */
scgets(buf, siz, lnum)
char buf[];
int siz, lnum;
{
	char c;
	int i, j, n;

	if (lnum > ROWS)
		return(-1);

	j = sPOS(0, lnum-1);		/* first column */
	n = sPOS(o_ncols-1, lnum-1);	/* last column */

	/* find last non-blank char in line */
	for (; j < n; n--)
		if ((c = o_scrn[n] & 0x7f) != NUL && c != ' ')
			{ n++; break; }

	/* save text to buf */
	for (i=0; i < siz && j < n; i++, j++)
		buf[i] = o_scrn[j] & 0x7f;

	buf[i] = NUL;
	return(i);
}

/* write string to status line */
uchar c_stat = 3;

sturp(s, x, y)
char *s;
int x, y;
{
	int i=0, c;

	if (interlace) return;
	
#ifndef VIDMEM
	if (!c_stat) {
		register char *p = s;

		while (*p) *p++ = ' ';
		burps(s, 1, ROWS+1, 0);
	}
	else {
		c = c_rev; c_rev = c_stat; i = rattr;
		burps(s, 1, ROWS+1, AT_REV);
		c_rev = c; rattr = i;
	}
#else
	c = (c_stat) ? (c_stat << 4) : 0; /* reverse video or blank */
	scp = &scrn[sPOS(0, ROWS)];
	r.h.dh = c;
	while (*s && i++ < ncols) {
		r.h.dl = *s++;
		*scp++ = r.x.dx;
	}
	r.h.dl = ' ';
	while (i++ < ncols)
		*scp++ = r.x.dx;
#endif
	GO_XY(x-1, y-1)
}


/* delay for n milliseconds */

dsleep(n)
unsigned int n;
{
#ifdef notdef /* #ifdef PCAT	*/
	unsigned long m;

	m = n * 1000;
	r.h.ah = 86;
	r.x.cx = m >> 16;
	r.x.dx = m & 0xffff;
	int86(0x15, &r, &r);
#else
	unsigned int x;

	while(n--)
		for (x=0x400; x; x--) ;	/* ... just a guess */
#endif
}


/* clear screen & home cursor */

clr_home()
{
	GO_HOME
	if (interlace) return;
	CLEAN(ncols * (ROWS+1))
}

/* dburp -- put debug message on status line */

dburp(s)
char *s;
{
	uchar x, y;

	xypos(&x, &y);
	sturp(s, x, y);
}

/*
 * honk: sound the horn
 */
#define TimerCtl	0x43
#define Timer2Cnt	0x42
#define BPort		0x61

honk(tdiv, n)
unsigned int tdiv, n;
{
	unsigned char bsav;
	register unsigned int i;
	register unsigned char c, x;

	c = tdiv >> 8;
	x = tdiv & 0xff;
	outportb(TimerCtl, 0xb6);	/* select timer 2 */
	outportb(Timer2Cnt, x);	/* timer 2 counter lsb */
	outportb(Timer2Cnt, c);	/* timer 2 counter msb */
	c = inportb(BPort);	/* save 8255 port b setting */
	bsav = c; c |= 3;
	outportb(BPort, c);	/* turn speaker on */
	while (n--) for(x=1; x; x++) ;
	outportb(BPort, bsav);	/* restore 8255 port */
}

#ifdef PLAY

#define sqrt_2	1.059463994	/* 12th root of 2 */

/*
 * 12 notes	: c d# d e# e f g# g a# a b# b
 *  per octave	: 0 1  2 3  4 5 6  7 8  9 10 11
 */
static int notes[7] = { 9, 11, 0, 2, 4, 5, 7};
/*			a  b   c  d  e  f  g	*/

play(p)
char *p;
{
	int note, octv, tlen;
	float freq;
	long clk;

	tlen = 10;

	/*  expecting: n[#]o.l{,n[#]o.l}
	 *  n == note [a-g],
	 * [#] == optional sharp,
	 *  o == octave [0-..],
	 *  l == duration [1-..]
	 */
	while (*p) {
		freq = 55.0;	/* low "C" == 55 Hz */
		clk = 1283000; 	/* PC/AT speaker clock speed */
		octv = 0;
		note = notes[*p - 'a'];
		if (*++p == '#') {
			p++;
			if (note) note--;
			else {	/* c# */
				octv = 1;
				note = notes['g'];
			}
		}
		octv += atoi(p);	while (*++p != '.') ;
		tlen = atoi(++p);	while (*p && *p++ != ',') ;

		for (; octv; octv--)
			freq *= 2;
	
		for (; note; note--)
			freq *= sqrt_2;
	
		note = clk/freq;
		honk(note, tlen);
	}
}
#endif /* PLAY */

/* IBM PC graphics nitty-gritty */

#ifdef BGI
#include "scr_gfx.bgi"
#else
/* for now, use bios calls until someone can
 * tell me how to handle graphics directly */
#ifndef BIOS
#define BIOS	1
#endif
#include "scr_gfx.h"
#endif /* BGI */
