/*
 *  Line printer driver
 */

#include "../h/param.h"
#include "../h/conf.h"
#include "../h/dir.h"
#include "../h/user.h"
#include "../h/tty.h"

/* Configuration */

#define LPPRI	75	/* line printer wakeup priority */
#define LPLWAT 70        /* line printer low water mark */
#define LPHWAT 150       /* line printer high water mark */

#define LPWIDTH	131	/* page width (not 132, as lp auto prints on 132nd char */
#define LPDEPTH	60	/* lines per page */

extern char	lpaddr;	/* printer address */

struct {    /* info about the printer */
	struct clist outq;	/* output char queue */
	char flag;	/* current mode */
	char state;	/* internal status */
	short mcc;	/* actual column posn */
	short ccc;	/* logical column posn */
	short mlc;	/* curr line # */
	short mxc;	/* max line width */
	short mxl;	/* max page depth */
} lp;

/* Printer status & commands */

#define ENABLE	0x40
#define DISARM	0xc0

#define NO_PAPER	0x40
#define INTLOCK		0x10
#define DU		0x01


#define	P_WASTE	ECHO	/* if on, printer wastes paper */
#define IND	CRMOD	/* if on, lines are indented 8 spaces */

#define FORM 0x0c
#define VTAB 0x0b


/*
 *    open - line printer
 */

lpopen(dev, flag)
{
	if(lp.state&ISOPEN || (ss(lpaddr)&(NO_PAPER|INTLOCK|DU))) {
		u.u_error = EIO;
		return;
	}

	trace(01<<8,"lpopen",0);

	lp.state = ISOPEN;
	if (lp.mxc == 0) {
		lp.mxc = LPWIDTH;
		lp.mxl = LPDEPTH;
	}
	oc(lpaddr, ENABLE);
	lpcanon(FORM);
}

/*
 *   close - line printer
 */

lpclose(dev, flag)
{

	lpcanon(FORM);
	trace(01<<8,"lpclose",0);
	lp.state = 0;
}

/*
 *     user write to line printer
 */

lpwrite()
{
	register c;

	while ((c=cpass()) >= 0)
		lpcanon(c);
}

/*
 * lpcanon - character transformations
 */

lpcanon(ac)
{
	register char c;
	register char *p;

	c = ac;

	if (c == '_')
		c |= 0x80;   /*
			      *   Interdata line printer i/face
			      *   turns x'5f' into x'3c' .....
			      *
			      *   But it isn't smart enough to
			      *   recognize x'df', fortunately.
			      */
	if (lp.flag & RAW) {
		lpoutput(c);
		return;
	}

	if (lp.flag & LCASE) {		/* Uppercase-only printer */
		if (c>='a' && c<='z')
			c += 'A'-'a';
		else {
			p = "({)}!|^~'`";
			while (*p++)
				if (c == *p++) {
					lpcanon(p[-2]);
					lp.ccc--;
					c = '-';
					break;
				}
		}
	}

	switch(c&0x7f) {

	case '\t':
		lp.ccc = (lp.ccc + 8) & ~7;
		return;

	case FORM:
	case '\n':
		if(lp.mcc != 0 || lp.mlc != 0 || lp.flag & P_WASTE) {
			if(lp.mcc) {
				lpoutput('\r');  /* cause line to print */
			}
			lp.mlc++;
			if(lp.mlc > lp.mxl && lp.mxl)
				c = FORM;
			lpoutput(c);
			if (c == FORM)
				lp.mlc = 0;
			lp.mcc = 0;
		}   /* now fall through to '\r' code */

	case '\r':
		lp.ccc = 0;
		if (lp.flag & IND)
			lp.ccc = 8;
		return;

	case '\b':
		if (lp.ccc > 0)
			lp.ccc--;
		return;

	case 0x07:
		lpoutput(c); /* ring bell, no char advance */
		return;

	case ' ':
		lp.ccc++;
		return;

	case VTAB:
		if(lp.mcc)
			lpcanon('\n');
		while(lp.mlc & 7)
			lpcanon('\n');
		return;

	default:
		if ((c & 0x7f) < 0x20)	/* non printing - ignore */
			return;
		if(lp.ccc < lp.mcc) {
			lpoutput('\r');    /* cause overprint */
			lp.mcc = 0;
		}
		if (lp.ccc < lp.mxc) {
			while(lp.ccc > lp.mcc) {
				lpoutput(' ');
				lp.mcc++;
			}
			lpoutput(c);
			lp.mcc++;
		}
		lp.ccc++;
	}
}


/*
 *   start transmission to printer
 */

lpstart()
{
	register c;
	register s;

	trace(02<<8,"lpstart",lpaddr);
	trace(01<<8,"lpstat",ss(lpaddr));

	while( (s = ss(lpaddr)) == 0
		&&    (c = getc(&lp.outq)) >= 0  ) {
			trace(010<<8,"lpchar",c);
			wd(lpaddr, c);
		}
	trace(01<<8,"lpstat",s);
}


/*
 *   line printer interrupt
 */

lpint(dev, stat)
{
	trace(02<<8,"lpint",stat);

/***	if(stat & DU)
		printf("\nline printer offline\n");
	else if (stat & NO_PAPER)
		printf("\nline printer paper out\n");
	else {			***/   if(!(stat & (DU|NO_PAPER))) {
		lpstart();
		if(lp.outq.c_cc <= LPLWAT && lp.state & ASLEEP) {
			trace(04<<8,"lpwakeup",lp.outq.c_cc);
			lp.state &= ~ASLEEP;
			wakeup(&lp);
		}
	}
}

/*
 *  write a character to line printer
 */

lpoutput(c)
{
	trace(02<<8,"lpoutput",c);
	spl4();
	while(lp.outq.c_cc >= LPHWAT) {
		lp.state |= ASLEEP;
		trace(04<<8,"lpsleep",lp.outq.c_cc);
		sleep(&lp, LPPRI);
	}

	putc(c, &lp.outq);
	lpstart();
	spl0();
}

/*
 * ioctl:
 *	allow several line printer attributes to be dynamically altered
 */

lpioctl(dev, cmd, addr, flag)
caddr_t addr;
{
	struct ttiocb iocb;
#ifdef SVC6
	struct v6iocb ioc6;
#endif

	switch (cmd) {

	case TIOCSETP:
	case TIOCSETN:
#ifdef SVC6
		if (u.u_svc6) {
			if (copyin(addr, (caddr_t)&ioc6, sizeof(ioc6))) {
				u.u_error = EFAULT;
				break;
			}
			lp.mxc = ioc6.ioc6_erase;
			lp.mxl = ioc6.ioc6_kill;
			lp.flag = ioc6.ioc6_flags;
			break;
		}
#endif
		if (copyin(addr, (caddr_t)&iocb, sizeof(iocb))) {
			u.u_error = EFAULT;
			break;
		}
		lp.mxc = iocb.ioc_erase;
		lp.mxl = iocb.ioc_kill;
		lp.flag = iocb.ioc_flags;
		break;

	case TIOCGETP:
#ifdef SVC6
		if (u.u_svc6) {
			ioc6.ioc6_erase = lp.mxc;
			ioc6.ioc6_kill = lp.mxl;
			ioc6.ioc6_flags = lp.flag;
			if (copyout((caddr_t)&ioc6, addr, sizeof(ioc6)))
				u.u_error = EFAULT;
			break;
		}
#endif
		iocb.ioc_erase = lp.mxc;
		iocb.ioc_kill = lp.mxl;
		iocb.ioc_flags = lp.flag;
		if (copyout((caddr_t)&iocb, addr, sizeof(iocb)))
			u.u_error = EFAULT;
		break;
	}
}
