/*
 * SPDX-License-Identifier: MIT
 * QEMU vt100
 */
#include "qemu/osdep.h"
#include "qemu/timer.h"
#include "cp437.h"
#include "vgafont.h"
#include "vt100.h"

#include "trace.h"

#define DEFAULT_BACKSCROLL 512
#define CONSOLE_CURSOR_PERIOD 500

static const pixman_color_t color_table_rgb[2][8] = {
    {   /* dark */
        [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK,
        [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xaa),  /* blue */
        [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0x00),  /* green */
        [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xaa, 0xaa),  /* cyan */
        [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0x00),  /* red */
        [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xaa, 0x00, 0xaa),  /* magenta */
        [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xaa, 0xaa, 0x00),  /* yellow */
        [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR_GRAY,
    },
    {   /* bright */
        [QEMU_COLOR_BLACK]   = QEMU_PIXMAN_COLOR_BLACK,
        [QEMU_COLOR_BLUE]    = QEMU_PIXMAN_COLOR(0x00, 0x00, 0xff),  /* blue */
        [QEMU_COLOR_GREEN]   = QEMU_PIXMAN_COLOR(0x00, 0xff, 0x00),  /* green */
        [QEMU_COLOR_CYAN]    = QEMU_PIXMAN_COLOR(0x00, 0xff, 0xff),  /* cyan */
        [QEMU_COLOR_RED]     = QEMU_PIXMAN_COLOR(0xff, 0x00, 0x00),  /* red */
        [QEMU_COLOR_MAGENTA] = QEMU_PIXMAN_COLOR(0xff, 0x00, 0xff),  /* magenta */
        [QEMU_COLOR_YELLOW]  = QEMU_PIXMAN_COLOR(0xff, 0xff, 0x00),  /* yellow */
        [QEMU_COLOR_WHITE]   = QEMU_PIXMAN_COLOR(0xff, 0xff, 0xff),  /* white */
    }
};

static bool cursor_visible_phase;
static QEMUTimer *cursor_timer;
static QTAILQ_HEAD(QemuVT100Head, QemuVT100) vt100s =
    QTAILQ_HEAD_INITIALIZER(vt100s);

static void image_fill_rect(pixman_image_t *image, int posx, int posy,
                            int width, int height, pixman_color_t color)
{
    pixman_rectangle16_t rect = {
        .x = posx, .y = posy, .width = width, .height = height
    };

    pixman_image_fill_rectangles(PIXMAN_OP_SRC, image,
                                 &color, 1, &rect);
}

/* copy from (xs, ys) to (xd, yd) a rectangle of size (w, h) */
static void image_bitblt(pixman_image_t *image,
                         int xs, int ys, int xd, int yd, int w, int h)
{
    pixman_image_composite(PIXMAN_OP_SRC,
                           image, NULL, image,
                           xs, ys, 0, 0, xd, yd, w, h);
}

static void vt100_putcharxy(QemuVT100 *vt, int x, int y, uint8_t ch,
                            TextAttributes *t_attrib)
{
    static pixman_image_t *glyphs[256];
    pixman_color_t fgcol, bgcol;

    assert(vt->image);
    if (t_attrib->invers) {
        bgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
        fgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
    } else {
        fgcol = color_table_rgb[t_attrib->bold][t_attrib->fgcol];
        bgcol = color_table_rgb[t_attrib->bold][t_attrib->bgcol];
    }

    if (!glyphs[ch]) {
        glyphs[ch] = qemu_pixman_glyph_from_vgafont(FONT_HEIGHT, vgafont16, ch);
    }
    qemu_pixman_glyph_render(glyphs[ch], vt->image,
                             &fgcol, &bgcol, x, y, FONT_WIDTH, FONT_HEIGHT);
}

static void vt100_invalidate_xy(QemuVT100 *vt, int x, int y)
{
    if (vt->update_x0 > x * FONT_WIDTH) {
        vt->update_x0 = x * FONT_WIDTH;
    }
    if (vt->update_y0 > y * FONT_HEIGHT) {
        vt->update_y0 = y * FONT_HEIGHT;
    }
    if (vt->update_x1 < (x + 1) * FONT_WIDTH) {
        vt->update_x1 = (x + 1) * FONT_WIDTH;
    }
    if (vt->update_y1 < (y + 1) * FONT_HEIGHT) {
        vt->update_y1 = (y + 1) * FONT_HEIGHT;
    }
}

static void vt100_show_cursor(QemuVT100 *vt, int show)
{
    TextCell *c;
    int y, y1;
    int x = vt->x;

    vt->cursor_invalidate = 1;

    if (x >= vt->width) {
        x = vt->width - 1;
    }
    y1 = (vt->y_base + vt->y) % vt->total_height;
    y = y1 - vt->y_displayed;
    if (y < 0) {
        y += vt->total_height;
    }
    if (y < vt->height) {
        c = &vt->cells[y1 * vt->width + x];
        if (show && cursor_visible_phase) {
            TextAttributes t_attrib = TEXT_ATTRIBUTES_DEFAULT;
            t_attrib.invers = !(t_attrib.invers); /* invert fg and bg */
            vt100_putcharxy(vt, x, y, c->ch, &t_attrib);
        } else {
            vt100_putcharxy(vt, x, y, c->ch, &(c->t_attrib));
        }
        vt100_invalidate_xy(vt, x, y);
    }
}

static void vt100_image_update(QemuVT100 *vt, int x, int y, int width, int height)
{
    vt->image_update(vt, x, y, width, height);
}

void vt100_refresh(QemuVT100 *vt)
{
    TextCell *c;
    int x, y, y1;
    int w = pixman_image_get_width(vt->image);
    int h = pixman_image_get_height(vt->image);

    vt->text_x[0] = 0;
    vt->text_y[0] = 0;
    vt->text_x[1] = vt->width - 1;
    vt->text_y[1] = vt->height - 1;
    vt->cursor_invalidate = 1;

    image_fill_rect(vt->image, 0, 0, w, h,
                    color_table_rgb[0][QEMU_COLOR_BLACK]);
    y1 = vt->y_displayed;
    for (y = 0; y < vt->height; y++) {
        c = vt->cells + y1 * vt->width;
        for (x = 0; x < vt->width; x++) {
            vt100_putcharxy(vt, x, y, c->ch,
                            &(c->t_attrib));
            c++;
        }
        if (++y1 == vt->total_height) {
            y1 = 0;
        }
    }
    vt100_show_cursor(vt, 1);
    vt100_image_update(vt, 0, 0, w, h);
}

static void vt100_scroll(QemuVT100 *vt, int ydelta)
{
    int i, y1;

    if (ydelta > 0) {
        for (i = 0; i < ydelta; i++) {
            if (vt->y_displayed == vt->y_base) {
                break;
            }
            if (++vt->y_displayed == vt->total_height) {
                vt->y_displayed = 0;
            }
        }
    } else {
        ydelta = -ydelta;
        i = vt->backscroll_height;
        if (i > vt->total_height - vt->height) {
            i = vt->total_height - vt->height;
        }
        y1 = vt->y_base - i;
        if (y1 < 0) {
            y1 += vt->total_height;
        }
        for (i = 0; i < ydelta; i++) {
            if (vt->y_displayed == y1) {
                break;
            }
            if (--vt->y_displayed < 0) {
                vt->y_displayed = vt->total_height - 1;
            }
        }
    }
    vt100_refresh(vt);
}

static void vt100_write(QemuVT100 *vt, const void *buf, size_t len)
{
    uint32_t num_free;

    num_free = fifo8_num_free(&vt->out_fifo);
    fifo8_push_all(&vt->out_fifo, buf, MIN(num_free, len));
    vt->out_flush(vt);
}

void vt100_set_image(QemuVT100 *vt, pixman_image_t *image)
{
    TextCell *cells, *c, *c1;
    int w1, x, y, last_width, w, h;

    vt->image = image;
    w = pixman_image_get_width(vt->image) / FONT_WIDTH;
    h = pixman_image_get_height(vt->image) / FONT_HEIGHT;
    if (w == vt->width && h == vt->height) {
        return;
    }

    last_width = vt->width;
    vt->width = w;
    vt->height = h;

    w1 = MIN(vt->width, last_width);

    cells = g_new(TextCell, vt->width * vt->total_height + 1);
    for (y = 0; y < vt->total_height; y++) {
        c = &cells[y * vt->width];
        if (w1 > 0) {
            c1 = &vt->cells[y * last_width];
            for (x = 0; x < w1; x++) {
                *c++ = *c1++;
            }
        }
        for (x = w1; x < vt->width; x++) {
            c->ch = ' ';
            c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
            c++;
        }
    }
    g_free(vt->cells);
    vt->cells = cells;
}

static void vt100_put_lf(QemuVT100 *vt)
{
    TextCell *c;
    int x, y1;

    vt->y++;
    if (vt->y >= vt->height) {
        vt->y = vt->height - 1;

        if (vt->y_displayed == vt->y_base) {
            if (++vt->y_displayed == vt->total_height) {
                vt->y_displayed = 0;
            }
        }
        if (++vt->y_base == vt->total_height) {
            vt->y_base = 0;
        }
        if (vt->backscroll_height < vt->total_height) {
            vt->backscroll_height++;
        }
        y1 = (vt->y_base + vt->height - 1) % vt->total_height;
        c = &vt->cells[y1 * vt->width];
        for (x = 0; x < vt->width; x++) {
            c->ch = ' ';
            c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
            c++;
        }
        if (vt->y_displayed == vt->y_base) {
            vt->text_x[0] = 0;
            vt->text_y[0] = 0;
            vt->text_x[1] = vt->width - 1;
            vt->text_y[1] = vt->height - 1;

            image_bitblt(vt->image, 0, FONT_HEIGHT, 0, 0,
                         vt->width * FONT_WIDTH,
                         (vt->height - 1) * FONT_HEIGHT);
            image_fill_rect(vt->image, 0, (vt->height - 1) * FONT_HEIGHT,
                            vt->width * FONT_WIDTH, FONT_HEIGHT,
                            color_table_rgb[0][TEXT_ATTRIBUTES_DEFAULT.bgcol]);
            vt->update_x0 = 0;
            vt->update_y0 = 0;
            vt->update_x1 = vt->width * FONT_WIDTH;
            vt->update_y1 = vt->height * FONT_HEIGHT;
        }
    }
}

/*
 * Set console attributes depending on the current escape codes.
 * NOTE: I know this code is not very efficient (checking every color for it
 * self) but it is more readable and better maintainable.
 */
static void vt100_handle_escape(QemuVT100 *vt)
{
    int i;

    for (i = 0; i < vt->nb_esc_params; i++) {
        switch (vt->esc_params[i]) {
        case 0: /* reset all console attributes to default */
            vt->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
            break;
        case 1:
            vt->t_attrib.bold = 1;
            break;
        case 4:
            vt->t_attrib.uline = 1;
            break;
        case 5:
            vt->t_attrib.blink = 1;
            break;
        case 7:
            vt->t_attrib.invers = 1;
            break;
        case 8:
            vt->t_attrib.unvisible = 1;
            break;
        case 22:
            vt->t_attrib.bold = 0;
            break;
        case 24:
            vt->t_attrib.uline = 0;
            break;
        case 25:
            vt->t_attrib.blink = 0;
            break;
        case 27:
            vt->t_attrib.invers = 0;
            break;
        case 28:
            vt->t_attrib.unvisible = 0;
            break;
        /* set foreground color */
        case 30:
            vt->t_attrib.fgcol = QEMU_COLOR_BLACK;
            break;
        case 31:
            vt->t_attrib.fgcol = QEMU_COLOR_RED;
            break;
        case 32:
            vt->t_attrib.fgcol = QEMU_COLOR_GREEN;
            break;
        case 33:
            vt->t_attrib.fgcol = QEMU_COLOR_YELLOW;
            break;
        case 34:
            vt->t_attrib.fgcol = QEMU_COLOR_BLUE;
            break;
        case 35:
            vt->t_attrib.fgcol = QEMU_COLOR_MAGENTA;
            break;
        case 36:
            vt->t_attrib.fgcol = QEMU_COLOR_CYAN;
            break;
        case 37:
            vt->t_attrib.fgcol = QEMU_COLOR_WHITE;
            break;
        /* set background color */
        case 40:
            vt->t_attrib.bgcol = QEMU_COLOR_BLACK;
            break;
        case 41:
            vt->t_attrib.bgcol = QEMU_COLOR_RED;
            break;
        case 42:
            vt->t_attrib.bgcol = QEMU_COLOR_GREEN;
            break;
        case 43:
            vt->t_attrib.bgcol = QEMU_COLOR_YELLOW;
            break;
        case 44:
            vt->t_attrib.bgcol = QEMU_COLOR_BLUE;
            break;
        case 45:
            vt->t_attrib.bgcol = QEMU_COLOR_MAGENTA;
            break;
        case 46:
            vt->t_attrib.bgcol = QEMU_COLOR_CYAN;
            break;
        case 47:
            vt->t_attrib.bgcol = QEMU_COLOR_WHITE;
            break;
        }
    }
}

static void vt100_update_xy(QemuVT100 *vt, int x, int y)
{
    TextCell *c;
    int y1, y2;

    vt->text_x[0] = MIN(vt->text_x[0], x);
    vt->text_x[1] = MAX(vt->text_x[1], x);
    vt->text_y[0] = MIN(vt->text_y[0], y);
    vt->text_y[1] = MAX(vt->text_y[1], y);

    y1 = (vt->y_base + y) % vt->total_height;
    y2 = y1 - vt->y_displayed;
    if (y2 < 0) {
        y2 += vt->total_height;
    }
    if (y2 < vt->height) {
        if (x >= vt->width) {
            x = vt->width - 1;
        }
        c = &vt->cells[y1 * vt->width + x];
        vt100_putcharxy(vt, x, y2, c->ch,
                      &(c->t_attrib));
        vt100_invalidate_xy(vt, x, y2);
    }
}

static void vt100_clear_xy(QemuVT100 *vt, int x, int y)
{
    int y1 = (vt->y_base + y) % vt->total_height;
    if (x >= vt->width) {
        x = vt->width - 1;
    }
    TextCell *c = &vt->cells[y1 * vt->width + x];
    c->ch = ' ';
    c->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
    vt100_update_xy(vt, x, y);
}

/*
 * UTF-8 DFA decoder by Bjoern Hoehrmann.
 * Copyright (c) 2008-2010 Bjoern Hoehrmann <bjoern@hoehrmann.de>
 * See https://github.com/polijan/utf8_decode for details.
 *
 * SPDX-License-Identifier: MIT
 */
#define BH_UTF8_ACCEPT 0
#define BH_UTF8_REJECT 12

static uint32_t bh_utf8_decode(uint32_t *state, uint32_t *codep, uint8_t byte)
{
    static const uint8_t utf8d[] = {
        /* character class lookup */
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,  0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,
        1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,  9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,9,
        7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,  7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,7,
        8,8,2,2,2,2,2,2,2,2,2,2,2,2,2,2,  2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,2,
        10,3,3,3,3,3,3,3,3,3,3,3,3,4,3,3, 11,6,6,6,5,8,8,8,8,8,8,8,8,8,8,8,

        /* state transition lookup */
        0,12,24,36,60,96,84,12,12,12,48,72, 12,12,12,12,12,12,12,12,12,12,12,12,
        12, 0,12,12,12,12,12, 0,12, 0,12,12, 12,24,12,12,12,12,12,24,12,24,12,12,
        12,12,12,12,12,12,12,24,12,12,12,12, 12,24,12,12,12,12,12,12,12,24,12,12,
        12,12,12,12,12,12,12,36,12,36,12,12, 12,36,12,12,12,12,12,36,12,36,12,12,
        12,36,12,12,12,12,12,12,12,12,12,12,
    };
    uint32_t type = utf8d[byte];

    *codep = (*state != BH_UTF8_ACCEPT) ?
        (byte & 0x3fu) | (*codep << 6) :
        (0xffu >> type) & (byte);

    *state = utf8d[256 + *state + type];
    return *state;
}

static void vt100_put_one(QemuVT100 *vt, uint8_t ch)
{
    TextCell *c;
    int y1;
    if (vt->x >= vt->width) {
        /* line wrap */
        vt->x = 0;
        vt100_put_lf(vt);
    }
    y1 = (vt->y_base + vt->y) % vt->total_height;
    c = &vt->cells[y1 * vt->width + vt->x];
    c->ch = ch;
    c->t_attrib = vt->t_attrib;
    vt100_update_xy(vt, vt->x, vt->y);
    vt->x++;
}

/* set cursor, checking bounds */
static void vt100_set_cursor(QemuVT100 *vt, int x, int y)
{
    if (x < 0) {
        x = 0;
    }
    if (y < 0) {
        y = 0;
    }
    if (y >= vt->height) {
        y = vt->height - 1;
    }
    if (x >= vt->width) {
        x = vt->width - 1;
    }

    vt->x = x;
    vt->y = y;
}

/**
 * vt100_csi_P() - (DCH) deletes one or more characters from the cursor
 * position to the right. As characters are deleted, the remaining
 * characters between the cursor and right margin move to the
 * left. Character attributes move with the characters.
 */
static void vt100_csi_P(QemuVT100 *vt, unsigned int nr)
{
    TextCell *c1, *c2;
    unsigned int x1, x2, y;
    unsigned int end, len;

    if (!nr) {
        nr = 1;
    }
    if (nr > vt->width - vt->x) {
        nr = vt->width - vt->x;
        if (!nr) {
            return;
        }
    }

    x1 = vt->x;
    x2 = vt->x + nr;
    len = vt->width - x2;
    if (len) {
        y = (vt->y_base + vt->y) % vt->total_height;
        c1 = &vt->cells[y * vt->width + x1];
        c2 = &vt->cells[y * vt->width + x2];
        memmove(c1, c2, len * sizeof(*c1));
        for (end = x1 + len; x1 < end; x1++) {
            vt100_update_xy(vt, x1, vt->y);
        }
    }
    /* Clear the rest */
    for (; x1 < vt->width; x1++) {
        vt100_clear_xy(vt, x1, vt->y);
    }
}

/**
 * vt100_csi_at() - (ICH) inserts `nr` blank characters with the default
 * character attribute. The cursor remains at the beginning of the
 * blank characters. Text between the cursor and right margin moves to
 * the right. Characters scrolled past the right margin are lost.
 */
static void vt100_csi_at(QemuVT100 *vt, unsigned int nr)
{
    TextCell *c1, *c2;
    unsigned int x1, x2, y;
    unsigned int end, len;

    if (!nr) {
        nr = 1;
    }
    if (nr > vt->width - vt->x) {
        nr = vt->width - vt->x;
        if (!nr) {
            return;
        }
    }

    x1 = vt->x + nr;
    x2 = vt->x;
    len = vt->width - x1;
    if (len) {
        y = (vt->y_base + vt->y) % vt->total_height;
        c1 = &vt->cells[y * vt->width + x1];
        c2 = &vt->cells[y * vt->width + x2];
        memmove(c1, c2, len * sizeof(*c1));
        for (end = x1 + len; x1 < end; x1++) {
            vt100_update_xy(vt, x1, vt->y);
        }
    }
    /* Insert blanks */
    for (x1 = vt->x; x1 < vt->x + nr; x1++) {
        vt100_clear_xy(vt, x1, vt->y);
    }
}

/**
 * vt100_save_cursor() - saves cursor position and character attributes.
 */
static void vt100_save_cursor(QemuVT100 *vt)
{
    vt->x_saved = vt->x;
    vt->y_saved = vt->y;
    vt->t_attrib_saved = vt->t_attrib;
}

/**
 * vt100_restore_cursor() - restores cursor position and character
 * attributes from saved state.
 */
static void vt100_restore_cursor(QemuVT100 *vt)
{
    vt->x = vt->x_saved;
    vt->y = vt->y_saved;
    vt->t_attrib = vt->t_attrib_saved;
}

static void vt100_putchar(QemuVT100 *vt, uint8_t ch)
{
    int i;
    int x, y;
    g_autofree char *response = NULL;

    switch (vt->state) {
    case TTY_STATE_NORM:
        if (ch >= 0x80 && vt->encoding == CHARDEV_VC_ENCODING_UTF8) {
            switch (bh_utf8_decode(&vt->utf8_state, &vt->utf8_codepoint, ch)) {
            case BH_UTF8_ACCEPT:
                vt100_put_one(vt, unicode_to_cp437(vt->utf8_codepoint));
                break;
            case BH_UTF8_REJECT:
                vt->utf8_state = BH_UTF8_ACCEPT;
                break;
            default:
                break;
            }
            break;
        }
        vt->utf8_state = BH_UTF8_ACCEPT;
        switch (ch) {
        case '\r':  /* carriage return */
            vt->x = 0;
            break;
        case '\n':  /* newline */
            vt100_put_lf(vt);
            break;
        case '\b':  /* backspace */
            if (vt->x > 0) {
                vt->x--;
            }
            break;
        case '\t':  /* tabspace */
            if (vt->x + (8 - (vt->x % 8)) > vt->width) {
                vt->x = 0;
                vt100_put_lf(vt);
            } else {
                vt->x = vt->x + (8 - (vt->x % 8));
            }
            break;
        case '\a':  /* alert aka. bell */
            /* TODO: has to be implemented */
            break;
        case 14:
            /* SO (shift out), character set 1 (ignored) */
            break;
        case 15:
            /* SI (shift in), character set 0 (ignored) */
            break;
        case 27:    /* esc (introducing an escape sequence) */
            vt->state = TTY_STATE_ESC;
            break;
        default:
            vt100_put_one(vt, ch);
            break;
        }
        break;
    case TTY_STATE_ESC: /* check if it is a terminal escape sequence */
        if (ch == '[') {
            for (i = 0; i < MAX_ESC_PARAMS; i++) {
                vt->esc_params[i] = 0;
            }
            vt->nb_esc_params = 0;
            vt->state = TTY_STATE_CSI;
        } else if (ch == '(') {
            vt->state = TTY_STATE_G0;
        } else if (ch == ')') {
            vt->state = TTY_STATE_G1;
        } else if (ch == ']' || ch == 'P' || ch == 'X'
                   || ch == '^' || ch == '_') {
            /* String sequences: OSC, DCS, SOS, PM, APC */
            vt->state = TTY_STATE_OSC;
        } else if (ch == '7') {
            vt100_save_cursor(vt);
            vt->state = TTY_STATE_NORM;
        } else if (ch == '8') {
            vt100_restore_cursor(vt);
            vt->state = TTY_STATE_NORM;
        } else {
            vt->state = TTY_STATE_NORM;
        }
        break;
    case TTY_STATE_CSI: /* handle escape sequence parameters */
        if (ch >= '0' && ch <= '9') {
            if (vt->nb_esc_params < MAX_ESC_PARAMS) {
                int *param = &vt->esc_params[vt->nb_esc_params];
                int digit = (ch - '0');

                *param = (*param <= (INT_MAX - digit) / 10) ?
                         *param * 10 + digit : INT_MAX;
            }
        } else {
            if (vt->nb_esc_params < MAX_ESC_PARAMS) {
                vt->nb_esc_params++;
            }
            if (ch == ';' || ch == '?') {
                break;
            }
            trace_console_putchar_csi(vt->esc_params[0], vt->esc_params[1],
                                      ch, vt->nb_esc_params);
            vt->state = TTY_STATE_NORM;
            switch (ch) {
            case 'A':
                /* move cursor up */
                if (vt->esc_params[0] == 0) {
                    vt->esc_params[0] = 1;
                }
                vt100_set_cursor(vt, vt->x, vt->y - vt->esc_params[0]);
                break;
            case 'B':
                /* move cursor down */
                if (vt->esc_params[0] == 0) {
                    vt->esc_params[0] = 1;
                }
                vt100_set_cursor(vt, vt->x, vt->y + vt->esc_params[0]);
                break;
            case 'C':
                /* move cursor right */
                if (vt->esc_params[0] == 0) {
                    vt->esc_params[0] = 1;
                }
                vt100_set_cursor(vt, vt->x + vt->esc_params[0], vt->y);
                break;
            case 'D':
                /* move cursor left */
                if (vt->esc_params[0] == 0) {
                    vt->esc_params[0] = 1;
                }
                vt100_set_cursor(vt, vt->x - vt->esc_params[0], vt->y);
                break;
            case 'G':
                /* move cursor to column */
                vt100_set_cursor(vt, vt->esc_params[0] - 1, vt->y);
                break;
            case 'f':
            case 'H':
                /* move cursor to row, column */
                vt100_set_cursor(vt, vt->esc_params[1] - 1, vt->esc_params[0] - 1);
                break;
            case 'J':
                switch (vt->esc_params[0]) {
                case 0:
                    /* clear to end of screen */
                    for (y = vt->y; y < vt->height; y++) {
                        for (x = 0; x < vt->width; x++) {
                            if (y == vt->y && x < vt->x) {
                                continue;
                            }
                            vt100_clear_xy(vt, x, y);
                        }
                    }
                    break;
                case 1:
                    /* clear from beginning of screen */
                    for (y = 0; y <= vt->y; y++) {
                        for (x = 0; x < vt->width; x++) {
                            if (y == vt->y && x > vt->x) {
                                break;
                            }
                            vt100_clear_xy(vt, x, y);
                        }
                    }
                    break;
                case 2:
                    /* clear entire screen */
                    for (y = 0; y < vt->height; y++) {
                        for (x = 0; x < vt->width; x++) {
                            vt100_clear_xy(vt, x, y);
                        }
                    }
                    break;
                }
                break;
            case 'K':
                switch (vt->esc_params[0]) {
                case 0:
                    /* clear to eol */
                    for (x = vt->x; x < vt->width; x++) {
                        vt100_clear_xy(vt, x, vt->y);
                    }
                    break;
                case 1:
                    /* clear from beginning of line */
                    for (x = 0; x <= vt->x && x < vt->width; x++) {
                        vt100_clear_xy(vt, x, vt->y);
                    }
                    break;
                case 2:
                    /* clear entire line */
                    for (x = 0; x < vt->width; x++) {
                        vt100_clear_xy(vt, x, vt->y);
                    }
                    break;
                }
                break;
            case 'P':
                vt100_csi_P(vt, vt->esc_params[0]);
                break;
            case 'm':
                vt100_handle_escape(vt);
                break;
            case 'n':
                switch (vt->esc_params[0]) {
                case 5:
                    /* report console status (always succeed)*/
                    vt100_write(vt, "\033[0n", 4);
                    break;
                case 6:
                    /* report cursor position */
                    response = g_strdup_printf("\033[%d;%dR",
                                               vt->y + 1, vt->x + 1);
                    vt100_write(vt, response, strlen(response));
                    break;
                }
                break;
            case 's':
                vt100_save_cursor(vt);
                break;
            case 'u':
                vt100_restore_cursor(vt);
                break;
            case '@':
                vt100_csi_at(vt, vt->esc_params[0]);
                break;
            default:
                trace_console_putchar_unhandled(ch);
                break;
            }
            break;
        }
        break;
    case TTY_STATE_OSC: /* Operating System Command: ESC ] ... BEL/ST */
        if (ch == '\a') {
            /* BEL terminates OSC */
            vt->state = TTY_STATE_NORM;
        } else if (ch == 27) {
            /* ESC might start ST (ESC \) */
            vt->state = TTY_STATE_ESC;
        }
        /* All other bytes are silently consumed */
        break;
    case TTY_STATE_G0: /* set character sets */
    case TTY_STATE_G1: /* set character sets */
        switch (ch) {
        case 'B':
            /* Latin-1 map */
            break;
        }
        vt->state = TTY_STATE_NORM;
        break;
    }

}

size_t vt100_input(QemuVT100 *vt, const uint8_t *buf, size_t len)
{
    int i;

    vt->update_x0 = vt->width * FONT_WIDTH;
    vt->update_y0 = vt->height * FONT_HEIGHT;
    vt->update_x1 = 0;
    vt->update_y1 = 0;
    vt100_show_cursor(vt, 0);
    for (i = 0; i < len; i++) {
        vt100_putchar(vt, buf[i]);
    }
    vt100_show_cursor(vt, 1);
    if (vt->update_x0 < vt->update_x1) {
        vt100_image_update(vt, vt->update_x0, vt->update_y0,
                           vt->update_x1 - vt->update_x0,
                           vt->update_y1 - vt->update_y0);
    }
    return len;
}

void vt100_keysym(QemuVT100 *vt, int keysym)
{
    uint8_t buf[16], *q;
    int c;

    switch (keysym) {
    case QEMU_KEY_CTRL_UP:
        vt100_scroll(vt, -1);
        break;
    case QEMU_KEY_CTRL_DOWN:
        vt100_scroll(vt, 1);
        break;
    case QEMU_KEY_CTRL_PAGEUP:
        vt100_scroll(vt, -10);
        break;
    case QEMU_KEY_CTRL_PAGEDOWN:
        vt100_scroll(vt, 10);
        break;
    default:
        /* convert the QEMU keysym to VT100 key string */
        q = buf;
        if (keysym >= 0xe100 && keysym <= 0xe11f) {
            *q++ = '\033';
            *q++ = '[';
            c = keysym - 0xe100;
            if (c >= 10) {
                *q++ = '0' + (c / 10);
            }
            *q++ = '0' + (c % 10);
            *q++ = '~';
        } else if (keysym >= 0xe120 && keysym <= 0xe17f) {
            *q++ = '\033';
            *q++ = '[';
            *q++ = keysym & 0xff;
        } else if (vt->echo && (keysym == '\r' || keysym == '\n')) {
            vt100_input(vt, (uint8_t *)"\r", 1);
            *q++ = '\n';
        } else {
            *q++ = keysym;
        }
        if (vt->echo) {
            vt100_input(vt, buf, q - buf);
        }
        vt100_write(vt, buf, q - buf);
        break;
    }
}

void vt100_update_cursor(void)
{
    QemuVT100 *vt;

    cursor_visible_phase = !cursor_visible_phase;

    if (QTAILQ_EMPTY(&vt100s)) {
        return;
    }

    QTAILQ_FOREACH(vt, &vt100s, list) {
        vt100_refresh(vt);
    }

    timer_mod(cursor_timer,
        qemu_clock_get_ms(QEMU_CLOCK_REALTIME) + CONSOLE_CURSOR_PERIOD / 2);
}

static void
cursor_timer_cb(void *opaque)
{
    vt100_update_cursor();
}

void vt100_init(QemuVT100 *vt,
                pixman_image_t *image,
                ChardevVCEncoding encoding,
                void (*image_update)(QemuVT100 *vt, int x, int y, int w, int h),
                void (*out_flush)(QemuVT100 *vt))
{
    if (!cursor_timer) {
        cursor_timer = timer_new_ms(QEMU_CLOCK_REALTIME, cursor_timer_cb, NULL);
    }

    vt->encoding = encoding;
    QTAILQ_INSERT_HEAD(&vt100s, vt, list);
    fifo8_create(&vt->out_fifo, 16);
    vt->total_height = DEFAULT_BACKSCROLL;
    vt->image_update = image_update;
    vt->out_flush = out_flush;
    /* set current text attributes to default */
    vt->t_attrib = TEXT_ATTRIBUTES_DEFAULT;
    vt100_set_image(vt, image);
}

void vt100_fini(QemuVT100 *vt)
{
    if (!QTAILQ_IN_USE(vt, list)) {
        return;
    }
    QTAILQ_REMOVE(&vt100s, vt, list);
    fifo8_destroy(&vt->out_fifo);
    g_free(vt->cells);
}
