/* vim: set filetype=c:
 *
 * ansi.c86  -  ANSI output routines for target system
 *
 * Copyright (C) 2003-2007 Gero Kuhlmann   <gero@gkminix.han.de>
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program; if not, write to the Free Software
 *  Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *
 * $Id: ansi.c86,v 1.6 2007/01/06 18:30:51 gkminix Exp $
 */


/* Include target system C headers */
#include <common.h86>
#include <i386/ctype.h86>
#include <i386/conio.h86>



/*
 ************************************************************************
 *
 * ANSI control sequences supported by the target library:
 *
 *                       Display attributes
 *
 *    Code          Effect
 *    <esc>[0m      normal text
 *    <esc>[1m      high-intensity on
 *    <esc>[21m     high-intensity off
 *    <esc>[5m      blinking on
 *    <esc>[25m     blinking off
 *    <esc>[7m      reverse video on
 *    <esc>[27m     reverse video off
 *    <esc>[3xm     set foreground color:
 *    <esc>[4xm     set background color. x can be:
 *
 *                       0 - black  4 - blue
 *                       1 - red    5 - magenta
 *                       2 - green  6 - cyan
 *                       3 - yellow 7 - white
 *
 *
 *                         Cursor control
 *
 *    Code          Effect
 *    <esc>[r;cH    move cursor to row r col c (r and c are both numbers)
 *    <esc>[rA      move cursor up r rows
 *    <esc>[rB      move cursor down r rows
 *    <esc>[cC      move cursor right c columns
 *    <esc>[cD      move cursor left c columns
 *    <esc>[3l      turn off control display
 *    <esc>[3h      turn on control display
 *    <esc>[7l      turn off line wrap
 *    <esc>[7h      turn on line wrap
 *    <esc>[J       clear screen and home cursor
 *    <esc>[s       save the cursor position
 *    <esc>[u       return cursor to saved position
 *
 ************************************************************************
 */


/*
 * Local definitions
 */
#define ARGMAX		2		/* max. number of command arguments */
#define LINEMAX		24		/* max. line number */



/*
 * Mode flags
 */
#define FLAG_NOWRAP	0x01		/* flag indicating not to wrap */
#define FLAG_NOCONTROL	0x02		/* flag indicating not to use control */
#define FLAG_NOCRLF	0x04		/* flag indicating not to use CR/LF */
#define FLAG_INVERS	0x10		/* flag for invers video */
#define FLAG_BLINK	0x20		/* flag for blinking */
#define FLAG_INTENSE	0x40		/* flag for high intensity */
#define FLAG_NORMAL	0x07		/* mask to set flags to normal mode */



/*
 * State of ANSI interpreter
 */
typedef enum {
	STATE_NORMAL,			/* normal character printing state */
	STATE_ESCAPE,			/* got an escape character */
	STATE_BRACKET,			/* got a bracket character */
	STATE_NUMBER			/* got a number */
} ansistate;



/*
 * Local variables
 */
static ansistate curstate;		/* current interpreter state */
static unsigned short scnpage;		/* current screen page */
static unsigned short scnattr;		/* current character attributes */
static unsigned short scncolumns;	/* current number of columns */
static unsigned short curpos;		/* current cursor position */
static unsigned short savedpos;		/* saved cursor position */
static unsigned short fg_color;		/* foreground color */
static unsigned short bg_color;		/* background color */
static unsigned char flags;		/* mode flags */




/*
 ************************************************************************
 *
 *                     BIOS interface functions
 *
 ************************************************************************
 */

/*
 * Get number of screen columns and the current screen page
 */
__nb_inline__ void getscncols(void)
{
  __asm__ __volatile__(
	"movb $0x0F,%%ah\n\t"
	"int $0x10\n\t"
	"movb %%ah,%%al\n\t"
	"xorb %%ah,%%ah\n\t"
	"xorb %%bl,%%bl"
	: "=&a" (scncolumns), "=b" (scnpage)
	: /* no input */
	: "cc", "memory");
}



/*
 * Scroll the screen
 */
__nb_inline__ void scrollscn(unsigned short lines)
{
  __u16 d0, d1, d2;

  d0 = (__u16)((lines & 0x00ff) | 0x0600);
  d1 = (__u16)((LINEMAX << 8) | ((scncolumns - 1) & 0x00ff));
  d2 = (__u16)(scnattr << 8);
  __asm__ __volatile__(
	"int $0x10"
	: /* no output */
	: "a" (d0), "b" (d2), "c" (0), "d" (d1)
	: "memory", "cc");
}



/*
 * Get cursor position from BIOS
 */
__nb_inline__ void getcursor(void)
{
  __u16 d0;

  __asm__ __volatile__(
	"movb $0x03,%%ah\n\t"
	"int $0x10"
	: "=&a" (d0), "=d" ((__u16)curpos)
	: "b" ((__u16)scnpage)
	: "cc", "memory");
}



/*
 * Set cursor position using the BIOS
 */
static void setcursor(short x, short y)
{
  unsigned short tmppos;
  __u16 d0;

  /* Check for correct parameters */
  if (x >= scncolumns)
	x = scncolumns - 1;
  else if (x < 0)
	x = 0;
  if (y > LINEMAX)
	y = LINEMAX;
  else if (y < 0)
	y = 0;

  /* Now move the cursor to it's new position */
  tmppos = (unsigned short)((x & 0x00ff) | ((y & 0x00ff) << 8));
  if (tmppos != curpos) {
	curpos = tmppos;
	__asm__ __volatile__(
		"movb $0x02,%%ah\n\t"
		"int $0x10"
		: "=&a" (d0)
		: "b" ((__u16)scnpage), "d" ((__u16)tmppos)
		: "cc", "memory");
  }
}



/*
 * Print a character without moving the cursor
 */
static void dochar(char ch, short *x, short *y)
{
  short i;
  __u16 d0, d1;

  /* Check if we have to print control characters verbatim */
  if ((flags & FLAG_NOCONTROL))
	goto dispchar;

  /* Handle tab character recursively */
  if (ch == CHR_TAB) {
	i = 8 - (*x % 8);
	while (i > 0) {
		setcursor(*x, *y);
		dochar(' ', x, y);
		i--;
	}
	return;
  }

  /* Sound a bell */
  if (ch == CHR_BELL) {
	d0 = (__u16)(((int)ch & 0x00ff) | 0x0e00);
	__asm__ __volatile__(
		"int $0x10"
		: /* no output */
		: "a" (d0), "b" ((__u16)scnpage)
		: "cc", "memory");
	return;
  }

  /* Handle cursor movement commands */
  if (ch == CHR_BS) {
	*x -= 1;
	return;
  }
  if (ch == CHR_CR) {
	*x = 0;
	return;
  }
  if (ch == CHR_LF) {
	if (!(flags & FLAG_NOCRLF))
		*x = 0;
	*y += 1;
	goto needscroll;
  }

  /* All other characters get printed without moving the cursor */
dispchar:
  d0 = (__u16)(((int)ch & 0x00ff) | 0x0900);
  d1 = (__u16)((scnpage & 0xff00) | (scnattr & 0x00ff));
  __asm__ __volatile__(
	"int $0x10"
	: /* no output */
	: "a" (d0), "b" (d1), "c" (1)
	: "cc", "memory");

  /* Now handle cursor movement and wrapping */
  *x += 1;
  if (*x >= scncolumns && !(flags & FLAG_NOWRAP)) {
	*x = 0;
	*y += 1;
  }

  /* Check if we need to scroll the screen */
needscroll:
  if (*y > LINEMAX) {
	*y = LINEMAX;
	scrollscn(1);
  }
}




/*
 ************************************************************************
 *
 *                     ANSI handling routines
 *
 ************************************************************************
 */

/*
 * Print a character, optionally handling ANSI sequences
 */
void ansiout(char ch)
{
  static short argnum = 0;
  static short argbuf[ARGMAX];
  short i, x, y;

  getcursor();
  x = (short)(curpos & 0x00ff);
  y = (short)((curpos & 0xff00) >> 8);
  switch (curstate) {
	case STATE_NORMAL:
		if (ch == CHR_ESC)
			curstate = STATE_ESCAPE;
		else if ((unsigned char)ch == ((unsigned char)CHR_ESC + 128))
			curstate = STATE_BRACKET;
		else
			dochar(ch, &x, &y);
		break;
	case STATE_ESCAPE:
		if (ch == '[')
			curstate = STATE_BRACKET;
		else {
			if (ch == 'E')
				x = 0;
			else if (ch == 'D')
				dochar(CHR_LF, &x, &y);
			curstate = STATE_NORMAL;
		}
		break;
	case STATE_BRACKET:
		if (ch == '?' || ch == '=')
			break;
		curstate = STATE_NUMBER;
		argnum = 0;
		for (i = 0; i < ARGMAX; i++)
			argbuf[i] = 0;
		/* fall through */
	case STATE_NUMBER:
		if (ch == ':') {
			argnum++;
			if (argnum < ARGMAX)
				break;
		} else if (ch >= '0' && ch <= '9') {
			argbuf[argnum] = argbuf[argnum] * 10 + (ch - '0');
			break;
		} else switch (ch) {
			case 'A':
				/* move cursor up */
				y -= argbuf[0];
				break;
			case 'B':
				/* move cursor down */
				y += argbuf[0];
				break;
			case 'C':
				/* move cursor right */
				x += argbuf[0];
				break;
			case 'D':
				/* move cursor left */
				x -= argbuf[0];
				break;
			case 'H':
				/* move cursor to specific position */
				x = argbuf[0];
				y = argbuf[1];
				break;
			case 'J':
				/* clear screen */
				scrollscn(0);
				x = 0;
				y = 0;
				break;
			case 'h':
				/* set display options */
				if (argbuf[0] == 7)
					/* turn on line wrapping */
					flags &= ~FLAG_NOWRAP;
				else if (argbuf[0] == 3)
					/* turn on control actions */
					flags &= ~FLAG_NOCONTROL;
				else if (argbuf[0] == 20)
					/* turn on CR/LF mode */
					flags &= ~FLAG_NOCRLF;
				break;
			case 'l':
				/* reset display options */
				if (argbuf[0] == 7)
					/* turn off line wrapping */
					flags |= FLAG_NOWRAP;
				else if (argbuf[0] == 3)
					/* turn off control actions */
					flags |= FLAG_NOCONTROL;
				else if (argbuf[0] == 20)
					/* turn off CR/LF mode */
					flags |= FLAG_NOCRLF;
				break;
			case 'm':
				/* set screen attributes */
				if (argbuf[0] >= 30 && argbuf[0] <= 37)
					fg_color = argbuf[0] - 30;
				else if (argbuf[0] >= 40 && argbuf[0] <= 47)
					bg_color = argbuf[0] - 40;
				else switch (argbuf[0]) {
					case 0:
						flags &= FLAG_NORMAL;
						break;
					case 1:
						flags |= FLAG_INTENSE;
						break;
					case 21:
						flags &= ~FLAG_INTENSE;
						break;
					case 5:
						flags |= FLAG_BLINK;
						break;
					case 25:
						flags &= ~FLAG_BLINK;
						break;
					case 7:
						flags |= FLAG_INVERS;
						break;
					case 27:
						flags &= ~FLAG_INVERS;
						break;
					default:
						break;
				}
				scnattr = 0;
				if (flags & FLAG_BLINK)
					scnattr |= 0x80;
				if (flags & FLAG_INTENSE)
					scnattr |= 0x08;
				if (flags & FLAG_INVERS)
					scnattr |= (bg_color & 0x0007) |
					           ((fg_color & 0x0007) << 4);
				else
					scnattr |= (fg_color & 0x0007) |
					           ((bg_color & 0x0007) << 4);
				break;
			case 's':
				/* save cursor position */
				savedpos = (unsigned short)((x & 0x00ff) |
				                           ((y & 0x00ff) << 8));
				break;
			case 'u':
				/* restore cursor position */
				x = (short)(savedpos & 0x00ff);
				y = (short)((savedpos & 0xff00) >> 8);
				break;
			default:
				break;
		}
		curstate = STATE_NORMAL;
		break;
  }
  setcursor(x, y);
}



/*
 * Get current cursor position
 */
void ansigetpos(short *x, short *y)
{
  *x = (short)(curpos & 0x00ff);
  *y = (short)((curpos & 0xff00) >> 8);
}



/*
 * Set current cursor position
 */
void ansisetpos(short x, short y)
{
  setcursor(x, y);
}



/*
 * Initialize the ANSI output module
 */
void ansiinit(void)
{
  altout = &ansiout;
  flags = 0;
  curstate = STATE_NORMAL;
  scnattr = 0x0007;
  fg_color = 0x0007;
  bg_color = 0x0000;
  getscncols();
  getcursor();
}

