/* ----------------------------------------------------------------------- *
 *
 *   Copyright 2004-2008 H. Peter Anvin - All Rights Reserved
 *
 *   Permission is hereby granted, free of charge, to any person
 *   obtaining a copy of this software and associated documentation
 *   files (the "Software"), to deal in the Software without
 *   restriction, including without limitation the rights to use,
 *   copy, modify, merge, publish, distribute, sublicense, and/or
 *   sell copies of the Software, and to permit persons to whom
 *   the Software is furnished to do so, subject to the following
 *   conditions:
 *
 *   The above copyright notice and this permission notice shall
 *   be included in all copies or substantial portions of the Software.
 *
 *   THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 *   EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
 *   OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 *   NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
 *   HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
 *   WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 *   FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
 *   OTHER DEALINGS IN THE SOFTWARE.
 *
 * ----------------------------------------------------------------------- */

/*
 * ansi.c
 *
 * ANSI character code engine
 */

#include <string.h>
#include <colortbl.h>
#include "ansi.h"

static const struct term_state default_state = {
    .xy = {0, 0},
    .cindex = 0,		/* First color table entry */
    .vtgraphics = 0,
    .intensity = 1,
    .underline = 0,
    .blink = 0,
    .reverse = 0,
    .fg = 7,
    .bg = 0,
    .autocr = 1,		/* Mimic \n -> \r\n conversion by default */
    .saved_xy = {0, 0},
    .cursor = 1,
    .state = st_init,
    .pvt = 0,
    .nparms = 0,
};

/* DEC VT graphics to codepage 437 table (characters 0x60-0x7F only) */
static const char decvt_to_cp437[] = {
    0004, 0261, 0007, 0007, 0007, 0007, 0370, 0361,
    0007, 0007, 0331, 0277, 0332, 0300, 0305, 0304,
    0304, 0304, 0137, 0137, 0303, 0264, 0301, 0302,
    0263, 0363, 0362, 0343, 0330, 0234, 0007, 00
};

void __ansi_init(const struct term_info *ti)
{
    memcpy(ti->ts, &default_state, sizeof default_state);
}

void __ansi_putchar(const struct term_info *ti, uint8_t ch)
{
    const struct ansi_ops *op = ti->op;
    struct term_state *st = ti->ts;
    const int rows = ti->rows;
    const int cols = ti->cols;
    struct curxy xy = st->xy;

    switch (st->state) {
    case st_init:
	switch (ch) {
	case 1 ... 5:
	    st->state = st_tbl;
	    st->parms[0] = ch;
	    break;
	case '\a':
	    op->beep();
	    break;
	case '\b':
	    if (xy.x > 0)
		xy.x--;
	    break;
	case '\t':
	    {
		int nsp = 8 - (xy.x & 7);
		while (nsp--)
		    __ansi_putchar(ti, ' ');
	    }
	    return;		/* Cursor already updated */
	case '\n':
	case '\v':
	case '\f':
	    xy.y++;
	    if (st->autocr)
		xy.x = 0;
	    break;
	case '\r':
	    xy.x = 0;
	    break;
	case 127:
	    /* Ignore delete */
	    break;
	case 14:
	    st->vtgraphics = 1;
	    break;
	case 15:
	    st->vtgraphics = 0;
	    break;
	case 27:
	    st->state = st_esc;
	    break;
	default:
	    /* Print character */
	    if (ch >= 32) {
		if (st->vtgraphics && (ch & 0xe0) == 0x60)
		    ch = decvt_to_cp437[ch - 0x60];

		op->write_char(xy.x, xy.y, ch, st);
		xy.x++;
	    }
	    break;
	}
	break;

    case st_esc:
	switch (ch) {
	case '%':
	case '(':
	case ')':
	case '#':
	    /* Ignore this plus the subsequent character, allows
	       compatibility with Linux sequence to set charset */
	    break;
	case '[':
	    st->state = st_csi;
	    st->nparms = st->pvt = 0;
	    memset(st->parms, 0, sizeof st->parms);
	    break;
	case 'c':
	    /* Reset terminal */
	    memcpy(&st, &default_state, sizeof st);
	    op->erase(st, 0, 0, cols - 1, rows - 1);
	    xy.x = xy.y = 0;
	    st->state = st_init;
	    break;
	default:
	    /* Ignore sequence */
	    st->state = st_init;
	    break;
	}
	break;

    case st_csi:
	{
	    int p0 = st->parms[0] ? st->parms[0] : 1;

	    if (ch >= '0' && ch <= '9') {
		st->parms[st->nparms] = st->parms[st->nparms] * 10 + (ch - '0');
	    } else if (ch == ';') {
		st->nparms++;
		if (st->nparms >= ANSI_MAX_PARMS)
		    st->nparms = ANSI_MAX_PARMS - 1;
		break;
	    } else if (ch == '?') {
		st->pvt = 1;
	    } else {
		switch (ch) {
		case 'A':
		    {
			int y = xy.y - p0;
			xy.y = (y < 0) ? 0 : y;
		    }
		    break;
		case 'B':
		    {
			int y = xy.y + p0;
			xy.y = (y >= rows) ? rows - 1 : y;
		    }
		    break;
		case 'C':
		    {
			int x = xy.x + p0;
			xy.x = (x >= cols) ? cols - 1 : x;
		    }
		    break;
		case 'D':
		    {
			int x = xy.x - p0;
			xy.x = (x < 0) ? 0 : x;
		    }
		    break;
		case 'E':
		    {
			int y = xy.y + p0;
			xy.y = (y >= rows) ? rows - 1 : y;
			xy.x = 0;
		    }
		    break;
		case 'F':
		    {
			int y = xy.y - p0;
			xy.y = (y < 0) ? 0 : y;
			xy.x = 0;
		    }
		    break;
		case 'G':
		case '\'':
		    {
			int x = st->parms[0] - 1;
			xy.x = (x >= cols) ? cols - 1 : (x < 0) ? 0 : x;
		    }
		    break;
		case 'H':
		case 'f':
		    {
			int y = st->parms[0] - 1;
			int x = st->parms[1] - 1;

			xy.x = (x >= cols) ? cols - 1 : (x < 0) ? 0 : x;
			xy.y = (y >= rows) ? rows - 1 : (y < 0) ? 0 : y;
		    }
		    break;
		case 'J':
		    {
			switch (st->parms[0]) {
			case 0:
			    op->erase(st, xy.x, xy.y, cols - 1, xy.y);
			    if (xy.y < rows - 1)
				op->erase(st, 0, xy.y + 1, cols - 1, rows - 1);
			    break;

			case 1:
			    if (xy.y > 0)
				op->erase(st, 0, 0, cols - 1, xy.y - 1);
			    if (xy.y > 0)
				op->erase(st, 0, xy.y, xy.x - 1, xy.y);
			    break;

			case 2:
			    op->erase(st, 0, 0, cols - 1, rows - 1);
			    break;

			default:
			    /* Ignore */
			    break;
			}
		    }
		    break;
		case 'K':
		    {
			switch (st->parms[0]) {
			case 0:
			    op->erase(st, xy.x, xy.y, cols - 1, xy.y);
			    break;

			case 1:
			    if (xy.x > 0)
				op->erase(st, 0, xy.y, xy.x - 1, xy.y);
			    break;

			case 2:
			    op->erase(st, 0, xy.y, cols - 1, xy.y);
			    break;

			default:
			    /* Ignore */
			    break;
			}
		    }
		    break;
		case 'h':
		case 'l':
		    {
			int set = (ch == 'h');
			switch (st->parms[0]) {
			case 20:
			    st->autocr = set;
			    break;
			case 25:
			    st->cursor = set;
			    op->showcursor(st);
			    break;
			default:
			    /* Ignore */
			    break;
			}
		    }
		    break;
		case 'm':
		    {
			static const int ansi2pc[8] =
			    { 0, 4, 2, 6, 1, 5, 3, 7 };

			int i;
			for (i = 0; i <= st->nparms; i++) {
			    int a = st->parms[i];
			    switch (a) {
			    case 0:
				st->fg = 7;
				st->bg = 0;
				st->intensity = 1;
				st->underline = 0;
				st->blink = 0;
				st->reverse = 0;
				break;
			    case 1:
				st->intensity = 2;
				break;
			    case 2:
				st->intensity = 0;
				break;
			    case 4:
				st->underline = 1;
				break;
			    case 5:
				st->blink = 1;
				break;
			    case 7:
				st->reverse = 1;
				break;
			    case 21:
			    case 22:
				st->intensity = 1;
				break;
			    case 24:
				st->underline = 0;
				break;
			    case 25:
				st->blink = 0;
				break;
			    case 27:
				st->reverse = 0;
				break;
			    case 30 ... 37:
				st->fg = ansi2pc[a - 30];
				break;
			    case 38:
				st->fg = 7;
				st->underline = 1;
				break;
			    case 39:
				st->fg = 7;
				st->underline = 0;
				break;
			    case 40 ... 47:
				st->bg = ansi2pc[a - 40];
				break;
			    case 49:
				st->bg = 7;
				break;
			    default:
				/* Do nothing */
				break;
			    }
			}
		    }
		    break;
		case 's':
		    st->saved_xy = xy;
		    break;
		case 'u':
		    xy = st->saved_xy;
		    break;
		default:	/* Includes CAN and SUB */
		    break;	/* Drop unknown sequence */
		}
		st->state = st_init;
	    }
	}
	break;

    case st_tbl:
	st->parms[1] = 0;
	if (ch == '#')
	    st->state = st_tblc;
	else
	    st->state = st_init;
	break;

    case st_tblc:
	{
	    unsigned int n = (unsigned char)ch - '0';
	    const char *p;

	    if (n < 10) {
		st->parms[1] = st->parms[1] * 10 + n;

		if (!--st->parms[0]) {
		    if (st->parms[1] < console_color_table_size) {
			/* Set the color table index */
			st->cindex = st->parms[1];

			/* See if there are any other attributes we care about */
			p = console_color_table[st->parms[1]].ansi;
			if (p) {
			    st->state = st_esc;
			    __ansi_putchar(ti, '[');
			    __ansi_putchar(ti, '0');
			    __ansi_putchar(ti, ';');
			    while (*p)
				__ansi_putchar(ti, *p++);
			    __ansi_putchar(ti, 'm');
			}
		    }
		    st->state = st_init;
		}
	    } else {
		st->state = st_init;
	    }
	}
	break;
    }

    /* If we fell off the end of the screen, adjust */
    if (xy.x >= cols) {
	xy.x = 0;
	xy.y++;
    }
    while (xy.y >= rows) {
	xy.y--;
	op->scroll_up(st);
    }

    /* Update cursor position */
    op->set_cursor(xy.x, xy.y, st->cursor);
    st->xy = xy;
}
