/**************************************************
UZI (Unix Z80 Implementation) Kernel:  devtty.c
***************************************************/


#include <sys/unix.h>
#include <sys/extern.h>
#include <sys/hardware.h>
#include <sys/devtty.h>


oneterm *current_term = (void*)0;
oneterm vterms[NOTERMS];


int tty_read( minor, rawflag )
int16	minor;
int16	rawflag;
{
	int	nread;

	nread = 0;
	while( nread < udata.u_count )
	{
		for(;;)
		{
			di();
			if( remq(&(vterms[minor].ttyinq),udata.u_base) )
				break;
			psleep( &(vterms[minor].ttyinq) );
			if( udata.u_cursig || udata.u_ptab->p_pending )  /* messy */
			{
				udata.u_error = EINTR;
				return(-1);
			}
		}
		ei();   

		if( nread++ == 0 && *udata.u_base == '\004' )   /* ^D */
			return( 0 );

		if( vterms[minor].flags & CBREAK )
			break;

		if (*udata.u_base == '\n')
			break;
		++udata.u_base;
	} 
	return( nread );
}

int tty_write( minor, rawflag )
int16	minor;
int16	rawflag;
{
	int	towrite;

	towrite = udata.u_count;

	while (udata.u_count-- != 0)
	{
		for(;;)        /* Wait on the ^S/^Q flag */
		{
			di();
			ifnot( vterms[minor].stopflag )    
				break;
			psleep( &(vterms[minor].stopflag) );
			if( udata.u_cursig || udata.u_ptab->p_pending )  /* messy */
			{
				udata.u_error = EINTR;
				return( -1 );
			}
		}
		ei();   

		ifnot( vterms[minor].flshflag )
		{
			if( *udata.u_base == '\n' )
				_putc( &(vterms[minor]), '\r' );
			_putc(&(vterms[minor]), *udata.u_base);
		}
		++udata.u_base;
	}
	return( towrite );
}



int tty_open( minor )
int	minor;
{
	return( 0 );
}


int tty_close( minor )
int	minor;
{
	return( 0 );
}


int tty_ioctl( minor, req, data )
int	minor;
int	req;
int	data;
{
	switch( req )
	{
	case SETNEC:
		if( data )
			vterms[minor].flags |= NOECHO;
		else
			vterms[minor].flags &= ~NOECHO;
		break;
	case SETCBRK:
		if( data )
			vterms[minor].flags |= CBREAK;
		else
			vterms[minor].flags &= ~CBREAK;
		break;
	}

	return(0);
}



/* This tty interrupt routine checks to see if the uart receiver actually
caused the interrupt.  If so it adds the character to the tty input
queue, echoing and processing backspace and carriage return.  If the queue 
contains a full line, it wakes up anything waiting on it.  If it is totally
full, it beeps at the user. */

 /*
 |  With MAPUX, this is called only when a new character arrives from
 |  the keyboard, so no testing is necessary, and the character in
 |  question is passed as a parameter
*/
int tty_int( c )
char	c;
{
	char	oc;

	if( c == '\003' )       /* ^C */
		sendsig(NULL, SIGINT);
	else if( c == '\017' )  /* ^O */
		current_term->flshflag = !(current_term->flshflag);
	else if( c == '\023' )  /* ^S */
		current_term->stopflag = 1;
	else if (c == '\021')   /* ^Q */
	{
		current_term->stopflag = 0;
		wakeup( &(current_term->stopflag) );
	}
	else if( c == '\b' )
	{
		if( uninsq(&(current_term->ttyinq),&oc) )
		{
			if( oc == '\n' )
				insq( &(current_term->ttyinq), oc );   /* Don't erase past newline */
			else
			{
				if( (current_term->flags&NOECHO) == 0 )
				{
					_putc(current_term,'\b');
					_putc(current_term,' ');
					_putc(current_term,'\b');
				}
			}
		}
	}
	else
	{
		if( c == '\r' || c == '\n' )
		{
			c = '\n';
			if( (current_term->flags&NOECHO) == 0 )
				_putc(current_term, '\r' );
		}

		if( insq(&(current_term->ttyinq),c) )
		{
			if( (current_term->flags&NOECHO) == 0 )
				_putc(current_term,c);
		}
		else
			_putc(current_term,'\007');  /* Beep if no more room */
	}

	if( (c=='\n' || c=='\004') || (current_term->flags&CBREAK) )   /* ^D */
		wakeup( &(current_term->ttyinq) );
}


/* Big function */
_putc(term,c)
oneterm	*term;
char	c;
{
	if( term )
		termPrint( term, c );
}


 /*
 |  This amounts to a terminal emulator
 |  Hopefully it will eventually mutate into something
 |  at leace slightly compatable with a vt100.
 |  Written with multiple virtual terminals in mind.
*/

extern char Screen[20480];
extern char CharSet[2048];

/* Called once at start */
termInit( term, id )
oneterm *term;
int	id;
{
	int	x, y;
	term->id = id;
	term->cur_x = 0;
	term->cur_y = 0;
	for(x=0;x<80;x++)
		for(y=0;y<32;y++)
		{
			term->cmap[x][y] = 32;
			term->amap[x][y] = 0;
		}
	term->flags = 0;
	term->conbuf[0] = 0;	/* Control sequence store */
	term->tab_width = 8;
	term->height = 32;
	term->width = 80;
	term->ttyinq.q_base   = term->ttyinbuf;
	term->ttyinq.q_head   = term->ttyinbuf;
	term->ttyinq.q_tail   = term->ttyinbuf;
	term->ttyinq.q_size   = TTYSIZ;
	term->ttyinq.q_count  = 0;
	term->ttyinq.q_wakeup = TTYSIZ/2;
	if( current_term == (void*)0 )
		termSwitchTo( term );
}

/* Print a character on screen */
termPrint( term, c )
oneterm	*term;
char	c;
{
	int	i;
	char	*p;

	term->busy = 1;

	/* If we're collecting for the control buffer, just add it */
	if( term->flags & COLLECT )
	{
		term->conbuf[term->cbpos++] = c;
		if( (term->cbpos==CBSIZE) || termConReady(term) )
		{
			termRemoveCursor( term );
			termControl( term );
		}
		term->busy = 0;
		return;
	}

	termRemoveCursor( term );

	/* There are also some simple one-byte codes */
	switch( c )
	{
	case 0:	/* NUL */
		break;
	case 7:	/* BEL */
		if( term == current_term )
			for(i=0;i<2;i++)
				for(p=Screen;p<Screen+SCRLEN;p++)
					*p = ~*p;
		break;
	case 8:	/* BS */
		if( --term->cur_x < 0 )
			if( --term->cur_y < 0 )
				term->cur_y = term->cur_x = 0;
			else
				term->cur_x = 0;
		break;
	case 9: /* HT */
		termPrint( term, 32 );	/* At least one space */
		while( term->cur_x % term->tab_width )
			termPrint( term, 32 );
		break;
	case 10: /* LF */
		term->cur_x = 0;	/* LF = CR+LF */
		term->cur_y++;
		termScroll( term, UP );
		break;
	case 12: /* FF */
		termClear( term );
		term->cur_x = 0;
		term->cur_y = 0;
		break;
	case 13: /* CR */
		term->cur_x = 0;
		break;
	case 27: /* ESC */
		term->cbpos = 0;
		term->flags |= COLLECT;
		break;
	case 28: /* FS - cursor right (!) */
		if( ++term->cur_x >= term->width )
		{
			term->cur_x = 0;
			term->cur_y++; 
			termScroll( term, UP );
		}
		break;
	case 29: /* GS - cursor left */
		if( --term->cur_x < 0 )
		{
			term->cur_x = term->width-1;
			term->cur_y--;
			termScroll( term, DOWN );
		}
		break;
	case 30: /* RS - cursor up */
		if( --term->cur_y <0 )
			termScroll( term, DOWN );
		break;
	case 31: /* US - cursor down */
		if( ++term->cur_y >= term->height )
			termScroll( term, UP );
		break;
	default:
		if( c<32 )
			break;
		termPrescroll( term );
		termCharOut( term, c );
		if( ++term->cur_x >= term->width )
		{
			term->cur_x = 0;
			term->cur_y++;
		}
		break;
	}

	term->busy = 0;
}


 /*
 |  Functions from this point down will need some work to operate
 |  with multiple virtual terminals
*/

termScroll( term, up )
oneterm	*term;
int	up;
{
	if( up )
		while( term->cur_y >= term->height )
		{
			term->cur_y--;
			termMapUp(term);
			if( term==current_term )
				renderUp();
		}
	else
		while( term->cur_y < 0 )
		{
			term->cur_y++;
			termMapDown(term);
			if( term==current_term )
				renderDown();
		}
}


termPrescroll( term )
oneterm	*term;
{
	while( term->cur_y >= term->height )
	{
		term->cur_y--;
		termMapUp(term);
		if( term==current_term )
			renderUp();
	}
}

termMapUp( term )
oneterm *term;
{
	int	x, y;
	for(x=0;x<80;x++)
		for(y=0;y<31;y++)
		{
			term->cmap[x][y] = term->cmap[x][y+1];
			term->amap[x][y] = term->amap[x][y+1];
		}
	for(x=0;x<80;x++)
	{
		term->cmap[x][31] = 32;
		term->amap[x][31] = 0;
	}
}

termMapDown( term )
oneterm *term;
{
	int	x, y;
	for(x=0;x<80;x++)
		for(y=31;y;y--)
		{
			term->cmap[x][y] = term->cmap[x][y-1];
			term->amap[x][y] = term->amap[x][y-1];
		}
	for(x=0;x<80;x++)
	{
		term->cmap[x][0] = 32;
		term->amap[x][0] = 0;
	}
}

termClear( term )
oneterm *term;
{
	int	x, y;
	for(x=0;x<80;x++)
		for(y=0;y<32;y++)
		{
			term->cmap[x][y] = 32;
			term->amap[x][y] = 0;
		}
	if( term==current_term )
		renderClear();
}

termCharOut( term, c )
oneterm	*term;
char	c;
{
	u_char	mat[8];
	int	i;
	char	*sbase;

	if( current_term == term )
	{
		qcopy( CharSet+(c<<3), mat, 2 );

		if( term->flags & BOLD )
			for(i=0;i<8;i++)
				mat[i] |= mat[i]<<1;
		if( term->flags & ITALIC )
		{
			mat[0] = mat[0]>>2;
			mat[1] = mat[1]>>1;
			mat[2] = mat[2]>>1;
			mat[5] = mat[5]<<1;
			mat[6] = mat[6]<<1;
			mat[7] = mat[7]<<2;
		}
		if( term->flags & UNDER )
			mat[7] = 0xff;
		if( term->flags & INVER )
			for(i=0;i<8;i++)
				mat[i] ^= 0xff;
		
		sbase = Screen+term->cur_x+(term->cur_y*640);
		for( i=0; i<8; i++ )
		{
			*sbase = mat[i];
			sbase+=80;
		}
	}
	
	term->cmap[term->cur_x][term->cur_y] = c;
	term->amap[term->cur_x][term->cur_y] = term->flags;
}


termConReady( term )
oneterm	*term;
{
	if( term->conbuf[term->cbpos-1] == '.' )
		return(1);
	return(0);
}

termControl( term )
oneterm *term;
{
	int	nx, ny;

	/* Terminate the string */
	term->conbuf[term->cbpos] = 0;

	/* Assume first character is an ESC */
	switch( term->conbuf[0] )
	{
	case 'm':	/* Move cursor "Emxx,yy." */
		nx = atoi( &(term->conbuf[1]) );
		ny = atoi( &(term->conbuf[termFindChar(&(term->conbuf[0]),',')+1]) );
		if( (nx>=0) && (nx<term->width) )
			term->cur_x = nx;
		if( (ny>=0) && (ny<term->height) )
			term->cur_y = ny;
		break;
	case 'v':	/* Video style "Evnn." */
		nx = atoi( &(term->conbuf[1]) );
		if( (nx>=1) && (nx<=4) )
			term->flags |= (1<<nx);
		else if( nx==0 )
			term->flags &= 0x61;
		break;
	}

	term->flags &= ~COLLECT;
	term->cbpos = 0;

	return;
}

termFindChar( str, chr )
char	*str,
	chr;
{
	int	pos = 0;
	while( (*str) && (*str!=chr) )
	{
		pos++;
		str++;
	}
	if( *str )
		return( pos );
	return(0);
}

termFlashInt( term )
oneterm	*term;
{
	int	i;
	char	*pos;
	
	if( (current_term!=term) || (term->busy) )
		return;
	if( term->cur_y < term->height )
		pos = Screen + term->cur_x + (640*term->cur_y);
	else
		pos = Screen + term->cur_x + (640*term->height);
	for(i=0;i<8;i++)
	{
		*pos = ~*pos;
		pos += 80;
	}
	term->curstate = ~term->curstate;
	return;
}

termRemoveCursor( term )
oneterm *term;
{
	char	*pos;
	int	i;

	if( current_term != term )
		return;
	if( term->curstate )
	{
		if( term->cur_y < term->height )
			pos = Screen + term->cur_x + (640*term->cur_y);
		else
			pos = Screen + term->cur_x + (640*term->height);
		for(i=0;i<8;i++)
		{
			*pos = ~*pos;
			pos += 80;
		}
		term->curstate = 0;
	}
}

termSwitchTo( term )
oneterm	*term;
{
	int	x, y,
		oflags, ox, oy;
	oneterm	*oterm;
	termRemoveCursor(current_term);
	term->busy = 1;
	if( term != current_term )
	{
		oterm = current_term;
		current_term = term;
		oflags = term->flags;
		ox = term->cur_x;
		oy = term->cur_y;
		for(x=0;x<term->width;x++)
			for(y=0;y<term->height;y++)
			{
				if( (term->cmap[x][y] != oterm->cmap[x][y]) ||
				    (term->amap[x][y] != oterm->amap[x][y]) )
				{
					term->flags = term->amap[x][y];
					term->cur_x = x;
					term->cur_y = y;
					termCharOut( term, term->cmap[x][y] );
				}
			}
		term->flags = oflags;
		term->cur_x = ox;
		term->cur_y = oy;
		term->busy = 0;
	}
}


static int	metamode=0;
static char	metakeys=0;
static int	repeatwait=0;
static char	repeater=0;

extern char keymap[96][8];

termKeyInt()
{
	char	raw;
	int	metabit;

	if( ciaa_bits(8) == 0 )
		return;

	raw = ~(*CIAA_SDR);

	if( ( raw & 0xf0 ) != 0xc0 )
	{
		/* Non-meta */
		if( raw & 1 )
		{
			/* Key up */
			termKeyHandshake();
			repeater = 0;
		}
		else
		{
			/* Key down */
			int	rr = (raw>>1)&0x7f;
			termKeyHandshake();
			if( (metamode==3) && (rr==0x50) )
				dumpexcept( -1, 0, 0 );
			if( (rr>=0x50) && (rr<=0x53) )
			{
				repeater = 0;
				termSwitchTo( &(vterms[rr-0x50]) );
			}
			else
			{
				repeatwait = INITDELAY;
				repeater = keymap[rr][metamode];
				tty_int( repeater );
			}
		}
	}
	else
	{
		metabit = -1;
		switch( raw & 0xfe )
		{
		case 0xc0:	/* Shift 1 */
		case 0xc2:	/* Shift 2 */
			metabit = 0;
			break;
		case 0xc6:	/* Control */
			metabit = 1;
			break;
		case 0xc8:	/* L-Alt */
		case 0xca:	/* R-Alt */
			metabit = 2;
			break;
		case 0xcc:	/* L-Amiga */
			metabit = 3;
			break;
		case 0xce:	/* R-Amiga */
			metabit = 4;
			break;
		case 0xc4:	/* Caps lock */
			metabit = 5;
			break;
		}
		if( metabit != -1 )
			if( raw & 1 )
			{
				metakeys &= ~(1<<metabit);
				metamode = 0;
			}
			else
			{
				metakeys |= (1<<metabit);
				metamode = (metakeys==0x18) ? 7 : metabit+1;
			}
		else
			metamode = 0;
		termKeyHandshake();
	}
}


termKeyHandshake()
{
	*CIAA_CRA |= 0x40;
	key_delay();
	*CIAA_CRA = 0x20;
}


termRepInt()
{
	if( repeater )
		if( repeatwait-- <= 0 )
		{
			repeatwait = REPDELAY;
			tty_int( repeater );
		}
}

