#include <X11/Xutil.h>
#include <X11/keysym.h>

#include <glib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
#include <wctype.h>
#include <assert.h>

#include "emulator.h"
#include "resizeable_buf.h"
#include "pane.h"
#include "script.h"
#include "util.h"
#include "unix.h"
#include "x11.h"
#include "defaults.h"
#include "colors.h"
#include "pty-outfifo.h"
#include "debug.h"

static wchar_t const * const charset_uk =
    L" !\"\x00A3$%&'()*+,-./0123456789:;<=>?"
    L"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_"
    L"`abcdefghijklmnopqrstuvwxyz{|}~";

static wchar_t const * const charset_special =
    L" !\"#$%&'()*+,-./0123456789:;<=>?"
    L"@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^\x25AE"
    L"\x25C6\x2592\x2409\x240C\x240D\x240A\x00B0\x00B1"
    L"\x2424\x240B\x2518\x2510\x250C\x2514\x253C\x23BA"
    L"\x23BB\x2500\x23BC\x23BD\x251C\x2524\x2534\x252C"
    L"\x2502\x2264\x2265\x03C0\x2260\x00A3\x00B7";

static void emulate_sdata(struct emulate *e, wchar_t ch, sc_t * output);
static void dispatch_csi(struct emulate *e, wchar_t ch, sc_t *p);
static void dispatch_esc(struct emulate *e, wchar_t ch, sc_t *p);
static void emulate_set_cursor(struct emulate *e, int col, int row);


static const char * const default_color_names[] =  {
        "black",
        "red3",
        "green3",
        "yellow3",
        "blue3",
        "magenta3",
        "cyan3",
        "gray90",
        "gray30",
        "red",
        "green",
        "yellow",
        "blue",
        "magenta",
        "cyan",
        "white",
#include "256colors.h"
};


#define emulator_debug opt_debug

static struct color *colors[256];

static struct color *
color(int i)
{
    assert( i >= 0 && i < 256);
    if(!colors[i]) {
            if(conn)
                colors[i] = get_alloc_color(conn->dpy, DefaultColormap(conn->dpy,DefaultScreen(conn->dpy)), default_color_names[i]);
            else
                colors[i] = get_color(default_color_names[i]);
    }
    return colors[i];

}


static void
scroll(struct emulate *e, int begin, int end, int num, sc_t *p)
{
        if(begin == 0 && end == e->rows  && num > 0)
                pane_scroll(p->pane,num, false);
        else
                pane_partial_scroll(p->pane, begin, end - 1, num);
}


struct emulate *
emulate_create(void)
{
        struct emulate *e = malloc(sizeof(struct emulate));
        e->state = ground_state;

        e->cursor.col = e->cursor.row = 0;
        e->cursor.mode_decom = false;
        e->cursor.mode_decawm = true;
        e->cursor.charset = e->cursor.charset_save = 0;

        e->saved.col = e->saved.row = 0;
        e->saved.mode_decom = false;
        e->saved.mode_decawm = true;
        e->saved.charset = e->saved.charset_save = 0;

        e->mode_irm = false;
        e->mode_kam = false;
        e->mode_srm = true;
        e->mode_lnm = false;
        e->mode_decckm = false;
        e->mode_decanm = true;
        e->mode_deckpam = false;
        e->mode_dectcem = true;
        e->cols = e->rows = 0;
        RB_INIT(&e->v_param);
        RB_INIT(&e->v_chars);
        RB_INIT(&e->v_collect);
        memset(&e->blank_style, 0, sizeof(struct cell));
        //setup_colors();
        e->blank_style.fgcolor = DC_foreground; // colors[DEFAULT_FG];
        e->blank_style.bgcolor = DC_background; //colors[DEFAULT_BG];
        e->style = e->blank_style;
        e->scroll_begin = 0;
        e->scroll_end = 0;
        e->pty = NULL;
        e->charset[0] = NULL;
        e->charset[1] = charset_special;
        e->charset[2] = NULL;
        e->charset[3] = NULL;
        e->tabs = NULL;


        return e;
}

void
emulate_set_size(struct emulate * e, int c, int r)
{
        int i;
        bitfield_t *tabs;
        if (e->scroll_end == e->rows || e->scroll_end > r) e->scroll_end = r;
        if (e->scroll_begin >= e->scroll_end) e->scroll_begin = e->scroll_end - 1;
        //tabs.resize(c);
        //for (int i = e->cols; i < c; ++i) tabs[i] = (0 == i % 8);

        tabs = bitfield_calloc(c);
        for (i = 0; i < MIN(e->cols,c); ++i)
                change_bit(tabs,i,test_bit(e->tabs,i));

        for (i = e->cols; i < c; ++i)
                change_bit(tabs, i, (0 == i % 8));
        free(e->tabs);
        e->tabs = tabs;
        e->cols = c;
        e->rows = r;
        emulate_set_cursor(e, e->cursor.col, e->cursor.row);
        if(emulator_debug)
                fprintf(stderr,"Set size to %ix%i\n", c, r);
}


void
emulate_set_cursor(struct emulate *e, int col, int row)
{
    e->cursor.col = MIN(e->cols - 1, MAX(0, col));
    e->cursor.row = MIN(e->rows - 1, MAX(0, row));
    //pane_setcursor(output, MIN((e->cursor.col), e->cols - 1), e->cursor.row);
}



static inline int
is_printable(wchar_t ch)
{
    return (ch >= 0x20 && ch < 0x7F) || ch >= 0xA0;
    //    return iswprint(ch);
}

void
emulate_data(struct emulate *e, void const *text, int length, sc_t *output, bool latin1)
{
        int sz = latin1 ? sizeof(char) : sizeof(wchar_t);
        do {
                if (ground_state == e->state
                    &&  NULL == e->charset[e->cursor.charset]
                    &&  e->cursor.charset == e->cursor.charset_save) {
                        int i = 0;
                        while (i < length && e->cursor.col + i < e->cols && is_printable(latin1 ? ((char *)text)[i] : ((wchar_t *)text)[i]))
                                ++i;
                        if(i) {
                                if (e->mode_irm)
                                        pane_line_copy(output->pane,
                                                       e->cursor.col,
                                                       e->cursor.col + i, e->cursor.row,
                                                       e->cols - e->cursor.col - i);
                                if((!e->mode_irm) && latin1) {
                                        //fprintf(stderr, "(%i,%i) %i\r\n", e->cursor.col, e->cursor.row, i );
                                        script_text(output,text,i);
                                }
                                pane_settext_generic(output->pane, e->cursor.col, e->cursor.row, text, i, e->style, latin1);
                                e->cursor.col = e->cursor.col + i;
                                length -= i;
                                text += i*sz;
                        }
                }

                if (length > 0) {
                        emulate_sdata(e, latin1 ? *((char *)text) : *((wchar_t *)text), output);
                        text += sz;
                        --length;
                }
        }
        while (length > 0);
        //output->set_cursor(std::min(int(cursor.col), cols - 1), cursor.row);

        emulate_set_cursor(e, MIN((e->cursor.col), e->cols - 1), e->cursor.row);
        pane_showcursor(output->pane, e->mode_dectcem);
        pane_setcursor(output->pane, e->cursor.col, e->cursor.row);
}



static void
emulate_sdata(struct emulate *e, wchar_t ch, sc_t * output)
{
    switch (e->state) {
    case osc_string_state:
            if(ch == 0x07) { e->state = ground_state; return; } else break;
    case ground_state:

        //v_collect.clear();
        //v_param.resize(1);
        //v_param[0] = 0;
        RB_CLEAR(e->v_collect);
        rb_resize(&e->v_param, sizeof(int), false);
        RBP(int, &e->v_param)[0] = 0;

    case csi_param_state:
    case csi_ignore_state:
    case csi_intermediate_state:
    case csi_entry_state:
    case escape_intermediate_state:
    case escape_state:
        switch (ch) {
                   case 0x01: case 0x02: case 0x03:
        case 0x04: case 0x05: case 0x06:
        case 0x10: case 0x11: case 0x12: case 0x13:
        case 0x14: case 0x15: case 0x16: case 0x17:
                   case 0x19:
        case 0x1C: case 0x1D: case 0x1E: case 0x1F:
            fprintf(stderr, "unknown C0: %02X\n", (unsigned)ch);
            return;
        case 0x00:
            return;
        case 0x07: // BEL
            //output->bell();
            if(DB_bell && IS_ATTACHED()) XBell(conn->dpy, 75);
            return;
        case 0x08: // BS
            e->cursor.col = MAX(0, e->cursor.col - 1);
            return;
        case 0x09: // HT
            if (e->cursor.col < e->cols - 1)
                e->cursor.col = e->cursor.col + 1;
            while (e->cursor.col < e->cols - 1 && !test_bit(e->tabs,e->cursor.col))
                e->cursor.col = e->cursor.col + 1;
//            e->cursor.col = ((e->cursor.col + 8) / 8) * 8;
            return;
        case 0x0A: // LF
        case 0x0B: // VT
        case 0x0C: // FF
//            c_newline(e, output);
            if (e->cursor.row == e->scroll_end - 1)
                scroll(e, e->scroll_begin, e->scroll_end, 1, output);
            else if (e->cursor.row < e->rows - 1)
                e->cursor.row = e->cursor.row + 1;
            if (e->mode_lnm)
                e->cursor.col = 0;
            return;
        case 0x0D: // CR
            e->cursor.col = 0;
            return;
        case 0x0E: // SO
            if (emulator_debug) fputs("SO\n",stderr);
            e->cursor.charset = e->cursor.charset_save = 1;
            return;
        case 0x0F: // SI
            if (emulator_debug) fputs("SI\n",stderr);
            e->cursor.charset = e->cursor.charset_save = 0;
            return;
        }
    default:
        break;
    }

    switch (ch) {
    case 0x00: case 0x01: case 0x02: case 0x03:
    case 0x04: case 0x05: case 0x06: case 0x07:
    case 0x08: case 0x09: case 0x0A: case 0x0B:
    case 0x0C: case 0x0D: case 0x0E: case 0x0F:
    case 0x10: case 0x11: case 0x12: case 0x13:
    case 0x14: case 0x15: case 0x16: case 0x17:
               case 0x19:
    case 0x1C: case 0x1D: case 0x1E: case 0x1F:
        if (e->state == dcs_passthrough_state) break;
        return;

    case 0x18:
    case 0x1A:
    case 0x80: case 0x81: case 0x82: case 0x83:
    case 0x84: case 0x85: case 0x86: case 0x87:
    case 0x88: case 0x89: case 0x8A: case 0x8B:
    case 0x8C: case 0x8D: case 0x8E: case 0x8F:
               case 0x91: case 0x92: case 0x93:
    case 0x94: case 0x95: case 0x96: case 0x97:
               case 0x99: case 0x9A:
        e->state = ground_state;
        fprintf(stderr, "unknown C1: #%02X\n", (unsigned)ch);
        return;

    case 0x1B: e->state = escape_state; return;
    case 0x90: e->state = dcs_entry_state; return;
    case 0x9B: e->state = csi_entry_state; return;
    case 0x9C: e->state = ground_state; return;
    case 0x9D: e->state = osc_string_state; return;
    }

    switch (e->state) {
    case csi_param_state:
    case dcs_param_state:
        switch (ch) {
        case 0x3B:
            //v_param.push_back(0);
            RB_APPEND(int, &e->v_param, 0);
            return;
        case 0x30: case 0x31: case 0x32: case 0x33:
        case 0x34: case 0x35: case 0x36: case 0x37:
        case 0x38: case 0x39:
            {
            int param = RB_LAST(int, &e->v_param);
            param = 10 * param + (ch - 0x30);
            RB_LAST(int, &e->v_param) = param;
            }
            //int & param = v_param.back();
            //param = 10 * param + (ch - 0x30);
            return;
        }
    default:
        break;
    }

    switch (e->state) {
    case ground_state:
        if (is_printable(ch)) {
            if (e->cursor.col >= e->cols) {
                if (e->cursor.mode_decawm) {
                    e->cursor.col = 0;
                    emulate_sdata(e, 0x0A, output);
                } else
                    e->cursor.col = MAX(0, e->cols - 1);
            }

            if (ch >= 0x20 && ch < 0x7F
            &&  NULL != e->charset[e->cursor.charset])
                ch = (e->charset[e->cursor.charset])[ch - 0x20];

            if (e->mode_irm)
                pane_line_copy(output->pane,
                    e->cursor.col,
                    e->cursor.col + 1, e->cursor.row,
                    e->cols - e->cursor.col - 1);
            pane_settext_generic(output->pane, e->cursor.col, e->cursor.row, &ch, 1, e->style, false);
            e->cursor.col = e->cursor.col + 1;
            e->cursor.charset = e->cursor.charset_save;
        }
        return;

    case escape_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
            e->state = escape_intermediate_state;
            emulate_sdata(e, ch, output);
            return;
        case 0x50: e->state = dcs_entry_state; return;
        case 0x5B: e->state = csi_entry_state; return;
        case 0x5D: e->state = osc_string_state; return;
        case 0x58:
        case 0x5E:
        case 0x5F: e->state = misc_string_state; return;
        case 0x7F: return;
        default:
            e->state = ground_state;
            dispatch_esc(e, ch, output);
            return;
        }

    case escape_intermediate_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
                //v_collect.push_back(ch);
                RB_APPEND(wchar_t, &e->v_collect, ch);
                    return;
        case 0x7F:
                return;
        default:
                e->state = ground_state;
                dispatch_esc(e, ch, output);
                return;
        }

    case csi_entry_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
            e->state = csi_intermediate_state;
            emulate_sdata(e, ch, output);
            return;
        case 0x3A:
            e->state = csi_ignore_state;
            return;
        case 0x30: case 0x31: case 0x32: case 0x33:
        case 0x34: case 0x35: case 0x36: case 0x37:
        case 0x38: case 0x39:            case 0x3B:
            e->state = csi_param_state;
            emulate_sdata(e, ch, output);
            return;
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
            //v_collect.push_back(ch);
            RB_APPEND(wchar_t, &e->v_collect, ch);
            e->state = csi_param_state;
            return;
        case 0x7F:
            return;
        default:
            e->state = ground_state;
            dispatch_csi(e, ch, output);
            return;
        }

    case csi_ignore_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
        case 0x30: case 0x31: case 0x32: case 0x33:
        case 0x34: case 0x35: case 0x36: case 0x37:
        case 0x38: case 0x39: case 0x3A: case 0x3B:
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
        case 0x7F:
            return;
        default:
            e->state = ground_state;
            return;
        }

    case csi_intermediate_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
            //v_collect.push_back(ch);
            RB_APPEND(wchar_t, &e->v_collect, ch);
            return;
        case 0x30: case 0x31: case 0x32: case 0x33:
        case 0x34: case 0x35: case 0x36: case 0x37:
        case 0x38: case 0x39: case 0x3A: case 0x3B:
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
            e->state = csi_ignore_state;
            return;
        case 0x7F:
            return;
        default:
            e->state = ground_state;
            dispatch_csi(e, ch, output);
            return;
        }

    case csi_param_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
            e->state = csi_intermediate_state;
            emulate_sdata(e, ch, output);
            return;
        case 0x3A:
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
            e->state = csi_ignore_state;
            return;
        case 0x7F:
            return;
        default:
            e->state = ground_state;
            dispatch_csi(e, ch, output);
            return;
        }

    case misc_string_state:
        switch (ch) {
        case 0x9C:
            e->state = ground_state;
            return;
        default:
            return;
        }

    case osc_string_state:
        switch (ch) {
        case 0x00: case 0x01: case 0x02: case 0x03:
        case 0x04: case 0x05: case 0x06: case 0x07:
        case 0x08: case 0x09: case 0x0A: case 0x0B:
        case 0x0C: case 0x0D: case 0x0E: case 0x0F:
        case 0x10: case 0x11: case 0x12: case 0x13:
        case 0x14: case 0x15: case 0x16: case 0x17:
                   case 0x19:
        case 0x1C: case 0x1D: case 0x1E: case 0x1F:
            return;
        case 0x9C:
            e->state = ground_state;
            return; // TODO: osc_end
        default:
            return; // TODO: osc_put
        }

    case dcs_entry_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
            //v_collect.push_back(ch);
            RB_APPEND(wchar_t, &e->v_collect, ch);
            e->state = dcs_intermediate_state;
            return;
        case 0x3A:
            e->state = dcs_ignore_state;
            return;
        case 0x30: case 0x31: case 0x32: case 0x33:
        case 0x34: case 0x35: case 0x36: case 0x37:
        case 0x38: case 0x39:            case 0x3B:
            e->state = dcs_param_state;
            emulate_sdata(e, ch, output);
            return;
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
            //v_collect.push_back(ch);
            RB_APPEND(wchar_t, &e->v_collect, ch);
            e->state = dcs_param_state;
            return;
        default:
            e->state = dcs_passthrough_state;
            emulate_sdata(e, ch, output);
            return;
        }

    case dcs_ignore_state:
        switch (ch) {
        case 0x9C:
            e->state = ground_state;
            return;
        default:
            return;
        }

    case dcs_intermediate_state:
        switch (ch) {
        case 0x30: case 0x31: case 0x32: case 0x33:
        case 0x34: case 0x35: case 0x36: case 0x37:
        case 0x38: case 0x39: case 0x3A: case 0x3B:
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
            e->state = dcs_ignore_state;
            return;
        default:
            e->state = dcs_passthrough_state;
            emulate_sdata(e, ch, output);
            return;
        }

    case dcs_param_state:
        switch (ch) {
        case 0x20: case 0x21: case 0x22: case 0x23:
        case 0x24: case 0x25: case 0x26: case 0x27:
        case 0x28: case 0x29: case 0x2A: case 0x2B:
        case 0x2C: case 0x2D: case 0x2E: case 0x2F:
            //v_collect.push_back(ch);
            RB_APPEND(wchar_t, &e->v_collect, ch);
            e->state = dcs_intermediate_state;
            return;
        case 0x3A:
        case 0x3C: case 0x3D: case 0x3E: case 0x3F:
            e->state = dcs_ignore_state;
            return;
        case 0x7F:
            return;
        default:
            e->state = dcs_passthrough_state;
            emulate_sdata(e, ch, output);
            return;
        }

    case dcs_passthrough_state:
        switch (ch) {
        case 0x9C:
            e->state = ground_state;
            return;
        default:
            return; // TODO: dcs_put
        }

    case vt52_1_state:
        if (ch >= 0x20) {
            //v_collect.push_back(ch);
            RB_APPEND(wchar_t, &e->v_collect, ch);
            e->state = vt52_2_state;
        }
        else {
            e->state = ground_state;
            emulate_sdata(e, ch, output);
        }
        return;

    case vt52_2_state:
        e->state = ground_state;
//        if (ch >= 0x20 && !v_collect.empty()) {
//            cursor.row = std::min(rows - 1, v_collect.front() - 0x20);
//            cursor.col = std::min(cols - 1, ch - 0x20);
//            if (cursor.mode_decom)
//                cursor.row = scroll_begin + std::min(
//                    scroll_end - scroll_begin - 1,
//                    scroll_begin + cursor.row);
//        }
//        else
            emulate_sdata(e, ch, output);
        return;
    }
}

// #define PARAM(e, i) = RBP(int, &(e)->v_param)[i]
#define PARAM(e)  RBP(int, &(e)->v_param)
#define PARAMSZ(e)  RBL(int, &(e)->v_param)


// process a CSI initiated command. ch is the final character
// parameters have been accumulated in v_param and excess characters
// have been put into v_collect

void
dispatch_csi(struct emulate *e, wchar_t ch, sc_t *p)
{

        int i;
        bool flag;
        if(emulator_debug) {
                fprintf(stderr, "CSI ");
                for (int i = 0; i < RBL(int, &e->v_param); ++i)
                        fprintf(stderr, "%c%d", (i ? ' ' : '['), RBP(int, &e->v_param)[i]);
                fprintf(stderr, "] ");
                for (int i = 0; i < RBL(wchar_t, &e->v_collect); ++i)
                        fprintf(stderr, "%c", (char)RBP(wchar_t, &e->v_collect)[i]);
                fprintf(stderr, "%c\n", (unsigned)ch);
        }

        switch (ch) {
        case 'A': // CUU
                e->cursor.row = MAX(
                                    (e->cursor.row >= e->scroll_begin) ? e->scroll_begin : 0,
                                    e->cursor.row - MAX(1, RBP(int, &e->v_param)[0]));
                return;

        case 'B': // CUD
                e->cursor.row = MIN(
                                    (e->cursor.row >= e->scroll_end) ? e->rows - 1 : e->scroll_end - 1,
                                    e->cursor.row + MAX(1, RBP(int, &e->v_param)[0]));
                return;

        case 'C': // CUF
                e->cursor.col = MIN(e->cols - 1, e->cursor.col + MAX(1, RBP(int, &e->v_param)[0]));
                return;

        case 'D': // CUB
                e->cursor.col = MAX(0, e->cursor.col - MAX(1, RBP(int, &e->v_param)[0]));
                return;

        case 'f': // HVP
        case 'H': // CUP
                while (PARAMSZ(e) < 2) { RB_APPEND(int, &e->v_param, 0); }
                e->cursor.row = MAX(0, MIN(e->rows - 1, MAX(1, PARAM(e)[0]) - 1));
                e->cursor.col = MAX(0, MIN(e->cols - 1, MAX(1, PARAM(e)[1]) - 1));
                if (e->cursor.mode_decom)
                        e->cursor.row = MAX(0,
                                            MIN(e->scroll_end - 1, e->scroll_begin + e->cursor.row));
                return;

        case 'J': // ED
                switch (PARAM(e)[0]) {
                case 1:
                        i = e->cursor.row;
                        if (e->cursor.col < e->cols - 1) { dispatch_csi(e, 'K', p); --i; }
                        pane_clear(p->pane, 0, 0, e->cols, i + 1, e->style);
                        return;
                case 2:
                        pane_clear(p->pane, 0, 0, e->cols, e->rows, e->style);
                        return;
                default:
                        i = e->cursor.row;
                        if (e->cursor.col > 0) { dispatch_csi(e, 'K', p); ++i; }
                        pane_clear(p->pane, 0, i, e->cols, e->rows - i, e->style);
                        return;
                }

        case 'K': // EL
                switch (PARAM(e)[0]) {
                case 1:
                        pane_clear(p->pane, 0, e->cursor.row, e->cursor.col + 1, 1, e->style);
                        return;
                case 2:
                        pane_clear(p->pane, 0, e->cursor.row, e->cols, 1, e->style);
                        return;
                default:
                        pane_clear(p->pane, e->cursor.col, e->cursor.row, e->cols - e->cursor.col, 1, e->style);
                        return;
                }

        case 'm': // SGR

                {
                        struct cell s = e->style;
                        //s = new Style(*cursor.style);
                        for (int i = 0; i < PARAMSZ(e); ++i) {
                                int v = PARAM(e)[i];
                                switch (v) {
                                case 0: s = e->blank_style; break;
                                case 1: s.flag.bold = true; break;
                                case 2: s.flag.dim = true; break;  // new
                                case 3: s.flag.italic = true; break; // new
                                case 4: s.flag.underline = true; break;
                                case 5: s.flag.blink = true; break;
                                case 6: s.flag.rapidblink = true; break;  //new
                                case 7: s.flag.inverse = true; break;
                                case 21: s.flag.bold = false; break;
                                case 22: s.flag.dim = false; break;  // new
                                case 23: s.flag.italic = false; break; // new
                                case 24: s.flag.underline = false; break;
                                case 25: s.flag.blink = false; break;
                                case 26: s.flag.rapidblink = false; break;  //new
                                case 27: s.flag.inverse = false; break;
                                case 39: s.fgcolor = DC_foreground; break; // colors[DEFAULT_FG]; break;
                                case 49: s.bgcolor = DC_background; break; // colors[DEFAULT_BG]; break;
                                case 30 ... 37: s.fgcolor = color(v - 30); break;
                                case 40 ... 47: s.bgcolor = color(v - 40); break;
                                case 90 ... 97: s.fgcolor = color(v - 90 + 8); break;
                                case 100 ... 107: s.bgcolor = color(v - 100 + 8); break;
                                                  //case 48: s.flag.subscript = true; break; //new
                                                  //case 49: s.flag.superscript = true; break; //new
                                case 38:
                                case 48:
                                                  if(PARAMSZ(e) - (i + 1) >= 2 && PARAM(e)[i + 1] == 5) {
                                                          int c = PARAM(e)[i+2];
                                                          if(c < 0 || c >= 256)
                                                                  fprintf(stderr, "bad color: %d\n", c);
                                                          else {
                                                                  if(PARAM(e)[i] == 38)
                                                                          s.fgcolor = color(c);
                                                                  else
                                                                          s.bgcolor = color(c);
                                                                  i+=2;
                                                          }
                                                          break;
                                                  }

                                default:
                                                  fprintf(stderr, "unknown text mode: %d\n", PARAM(e)[i]);
                                                  break;
                                }
                        }
                        e->style = s;
                        return;
                }

        case 'h': // SM
        case 'l': // RM
                flag = (ch == 'h');
                for (int i = 0; i < PARAMSZ(e); ++i)
                        if (0 == RB_LEN(&e->v_collect) || RBP(wchar_t, &e->v_collect)[0] != '?')
                                switch (PARAM(e)[i]) {
                                case 0: break;
                                case 2: // KAM
                                        e->mode_kam = flag;
                                        break;
                                case 4: // IRM
                                        e->mode_irm = flag;
                                        break;
                                case 12: // SRM
                                        e->mode_srm = flag;
                                        break;
                                case 20: // LNM
                                        e->mode_lnm = flag;
                                        break;
                                default:
                                        fprintf(stderr, "unknown mode: %d %s\n",
                                                PARAM(e)[i], (flag ? "set" : "unset"));
                                        break;
                                }
                        else
                                switch (PARAM(e)[i]) {
                                case 0: break;
                                case 1: // DECCKM
                                        e->mode_decckm = flag;
                                        break;
                                case 2: // DECANM
                                        e->mode_decanm = flag;
                                        break;
                                case 3: break; // TODO: DECCOLM
                                case 4: break; // DECSLM
                                case 5: break; // TODO: DECSCNM
                                case 6: // DECOM
                                        e->cursor.mode_decom = flag;
                                        e->cursor.col = 0;
                                        e->cursor.row = flag ? e->scroll_begin : 0;
                                        break;
                                case 7: // DECAWM
                                        e->cursor.mode_decawm = flag;
                                        break;
                                case 8: break; // TODO: DECARM
                                case 12: break; // TODO: blinking cursor
                                case 18: break; // TODO: DECPFF
                                case 19: break; // TODO: DECPEX
                                case 25: e->mode_dectcem = flag; break;
                                case 1000: break; // mouse mode
                                case 1048:  // alternate screen
                                           fprintf(stderr, "Alt 1048 %s\n", (flag ? "set" : "unset"));
                                           break;
                                case 1049:  // alternate screen
                                           fprintf(stderr, "Alt 1049 %s\n", (flag ? "set" : "unset"));
                                           break;
                                default:
                                           fprintf(stderr, "unknown private mode: %d %s\n",
                                                   PARAM(e)[i], (flag ? "set" : "unset"));
                                           break;
                                }
                return;
        case 'd':  //VPA
                i = PARAM(e)[0];
                if (i > 0) i--;
                e->cursor.row = i;
                return;
        case 'G':  // CHA
                i = PARAM(e)[0];
                if (i > 0) i--;
                e->cursor.col = i;
                return;

        case 'r': // DECSTBM
                while (PARAMSZ(e) < 2) { RB_APPEND(int, &e->v_param, 0); }
                //while (v_param.size() < 2) v_param.push_back(0);
                e->scroll_begin = MAX(0, MIN(e->rows - 1, PARAM(e)[0] - 1));
                e->scroll_end = MAX(
                                    e->scroll_begin + 1,
                                    PARAM(e)[1] ? MIN(e->rows, PARAM(e)[1]) : e->rows);
                e->cursor.col = 0;
                e->cursor.row = e->cursor.mode_decom ? e->scroll_begin : 0;
                return;

        case 'L': // IL
                if (e->cursor.row < e->scroll_begin || e->cursor.row >= e->scroll_end) return;
                scroll(e, e->cursor.row, e->scroll_end, -MAX(1, PARAM(e)[0]), p);
                return;

        case 'M': // DL
                if (e->cursor.row < e->scroll_begin || e->cursor.row >= e->scroll_end) return;
                scroll(e, e->cursor.row, e->scroll_end, MAX(1, PARAM(e)[0]), p);
                return;
        case '@': //ICH
                i = PARAM(e)[0];
                if ( 0 == i) i = 1;
                pane_line_copy(p->pane, e->cursor.col, e->cursor.col + i, e->cursor.row, i);
                pane_clear(p->pane, e->cursor.col, e->cursor.row,  i,  1, e->blank_style);
                return;
        case 'X': //ECH
                i = PARAM(e)[0];
                if ( 0 == i) i = 1;
                pane_clear(p->pane, e->cursor.col, e->cursor.row, i, 1, e->style);
                return;

        case 'P': // DCH
                i = MIN(MAX(1, PARAM(e)[0]), e->cols - e->cursor.col);
                if (i <= 0) return;
                //pane_line_copy(p, e->cols - 1, e->cols - i, e->cursor.row, 1);
                pane_line_copy(p->pane,
                               e->cursor.col + i,
                               e->cursor.col, e->cursor.row,
                               e->cols - e->cursor.col - i);
                pane_clear(p->pane, e->cols - i, e->cursor.row, i, 1, e->blank_style);
                return;

        case 'c': // DA
                send_string(e->pty, "\e[?6c");
                return;
        case 'n': // DSR
                switch (PARAM(e)[0]) {
                case 5:
                        send_string(e->pty, "\e[0n");
                        return;
                case 6:
                        {
                                char pos_report[32];
                                sprintf(pos_report, "\e[%d;%dR", e->cursor.row + 1, e->cursor.col + 1);
                                send_string(e->pty, pos_report);
                                return;
                        }
                case 15:
                        send_string(e->pty, "\e[?13n");
                        return;
                default:
                        fprintf(stderr, "unknown status report: %d\n", PARAM(e)[0]);
                        return;
                }

        case 'y': // DECTST
                send_string(e->pty, "\e[0n");
                return;

        case 'Z': // Backtab
                if (e->cursor.col > 0)
                        e->cursor.col = e->cursor.col - 1;
                while (e->cursor.col > 0 && !test_bit(e->tabs,e->cursor.col))
                        e->cursor.col = e->cursor.col - 1;
                return;
        case 'g': // TBC
                if (3 == PARAM(e)[0])
                        bitfield_clear(e->tabs, e->cols);
                else if (0 == PARAM(e)[0]
                         &&  e->cursor.col >= 0 && e->cursor.col < e->cols)
                        clear_bit(e->tabs, e->cursor.col);
                return;


                /*
                   case 'g': // TBC
                   if (3 == v_param[0])
                   std::fill(tabs.begin(), tabs.end(), false);
                   else if (0 == v_param[0]
                   &&  cursor.col >= 0 && cursor.col < int(tabs.size()))
                   tabs[cursor.col] = false;
                   return;
                   */

        case 'S':  // scroll up
        case 'T':  // scroll down
                {
                        int amount;
                        if (!PARAM(e)[0])
                                amount = 1;
                        else
                                amount = PARAM(e)[0];
                        if (ch == 'T')
                                amount = -amount;

                        scroll(e, e->scroll_begin, e->scroll_end, amount , p);
                        return;
                }
        case 'p':  // DECSTR
                if(RBL(wchar_t, &e->v_collect) == 1 && RBP(wchar_t, &e->v_collect)[0] == '!') {
                        e->style = e->blank_style;
                        pane_clear(p->pane, 0, 0, e->cols, e->rows, e->style);
                        e->cursor.col = 0;
                        e->cursor.row = 0;
                        e->charset[0] = NULL;
                        e->charset[1] = charset_special;
                        e->charset[2] = NULL;
                        e->charset[3] = NULL;
                        return;
                }

        }
        fprintf(stderr, "unknown CSI: ");
        for (int i = 0; i < RBL(int, &e->v_param); ++i)
                fprintf(stderr, "%c%d", (i ? ' ' : '['), RBP(int, &e->v_param)[i]);
        fprintf(stderr, "] ");
        for (int i = 0; i < RBL(wchar_t, &e->v_collect); ++i)
                fprintf(stderr, "%c", (char)RBP(wchar_t, &e->v_collect)[i]);
        fprintf(stderr, "%c\n", (unsigned)ch);
}
void
dispatch_esc(struct emulate *e, wchar_t ch, sc_t *p)
{
        int i;
        if(emulator_debug) {
                fprintf(stderr, "ESC ");
                for (int i = 0; i < RBL(wchar_t, &e->v_collect); ++i)
                        fprintf(stderr, "%c", (char)RBP(wchar_t, &e->v_collect)[i]);
                fprintf(stderr, "%c\n", (unsigned)ch);
        }

        if (!RB_LEN(&e->v_collect)) {
                switch (ch) {
                case '<': // DECANM
                        e->mode_decanm = true;
                        return;
                case '=': // DECKPAM
                        e->mode_deckpam = true;
                        return;
                case '>': // DECKPNM
                        e->mode_deckpam = false;
                        e->mode_decckm = false;
                        return;
                case '7': // DECSC
                        e->saved = e->cursor;
                        return;
                case '8': // DECRC
                        e->cursor = e->saved;
                        return;
                case 'A':
                        if (e->cursor.row > 0 && e->cursor.row > e->scroll_begin)
                                e->cursor.row = e->cursor.row - 1;
                        return;
                case 'B':
                        if (e->cursor.row < e->rows - 1 && e->cursor.row < e->scroll_end - 1)
                                e->cursor.row = e->cursor.row + 1;
                        return;
                case 'C':
                        e->cursor.col = MIN(e->cols - 1, e->cursor.col + 1);
                        return;
                case 'D':
                        if (e->mode_decanm) { // IND
                                if (e->cursor.row == e->scroll_end - 1)
                                        scroll(e, e->scroll_begin, e->scroll_end, 1, p);
                                else if (e->cursor.row < e->rows - 1)
                                        e->cursor.row = e->cursor.row + 1;
                                return;
                        }
                        e->cursor.col = MAX(0, e->cursor.col - 1);
                        return;
                case 'E': // NEL
                        e->cursor.col = 0;
                        if (e->cursor.row == e->scroll_end - 1)
                                scroll(e, e->scroll_begin, e->scroll_end, 1, p);
                        else if (e->cursor.row < e->rows - 1)
                                e->cursor.row = e->cursor.row + 1;
                        return;
                case 'K':
                        pane_clear(p->pane, e->cursor.col, e->cursor.row, e->cols - e->cursor.col, 1, e->style);
                        return;
                case 'I':
                case 'M': // RI
                        if (e->cursor.row == e->scroll_begin)
                                scroll(e, e->scroll_begin, e->scroll_end, -1, p);
                        else if (e->cursor.row > 0)
                                e->cursor.row = e->cursor.row - 1;
                        return;
                case 'J':
                        i = e->cursor.row;
                        if (e->cursor.col > 0) { dispatch_csi(e, 'K', p); ++i; }
                        pane_clear(p->pane, 0, i, e->cols, e->rows - i, e->style);
                        return;
                case '\\':
                        emulate_sdata(e, 0x9C, p);  // ST
                        return;
                case 'H':
                        if (!e->mode_decanm) {
                                e->cursor.col = 0;
                                e->cursor.row = (e->cursor.mode_decom) ? e->scroll_begin : 0;
                        }
                        else if (e->cursor.col >= 0 && e->cursor.col < e->cols) // HTS
                                set_bit(e->tabs,e->cursor.col);
                        //else if (e->cursor.col >= 0 && e->cursor.col < int(tabs.size())) // HTS
                        //        e->tabs[e->cursor.col] = true;
                        return;
                case 'n':
                        e->cursor.charset = 2;
                        return;
                case 'o':
                        e->cursor.charset = 3;
                        return;
                case 'c': // RIS
                        e->style = e->blank_style;
                        pane_clear(p->pane, 0, 0, e->cols, e->rows, e->style);
                        e->cursor.col = 0;
                        e->cursor.row = 0;
                        e->charset[0] = NULL;
                        e->charset[1] = charset_special;
                        e->charset[2] = NULL;
                        e->charset[3] = NULL;

                        return;


                        /*
                           case 'F':
                           cursor.charset = cursor.charset_save = charset_special;
                           return;
                           case 'G':
                           cursor.charset = cursor.charset_save = Null<wchar_t const *>();
                           return;
                           case 'Y':
                           state = vt52_1_state;
                           return;
                           case 'Z': // DECID
                           stuff(L"\e/Z");
                           return;
                           case 'c': // RIS
                           c = cols; r = rows;
                         *this = Emulator();
                         set_size(c, r);
                         return; */
                }
        }
        else switch (RBP(wchar_t, &e->v_collect)[0]) {
        case '#':
                switch (ch) {
                case '8':
                        {
                                fprintf(stderr,"DEC self test\n");
                        struct cell e_char = e->style;
                        e_char.ch = 'E';
                        pane_clear(p->pane, 0, 0, e->cols, e->rows, e_char);
                        return;
                        }
                        /*
                case '8':
                        v_chars.resize(cols);
                        fill(v_chars.begin(), v_chars.end(), 'E');
                        for (int r = 0; r < rows; ++r)
                                output->set_text(0, r, cols, &v_chars[0], cursor.style);
                        return;
                        */
                }
                break;

        case '(': // SCS
        case ')':
        case '*':
        case '+':
                {
                        char *ptr = "()*+";
                        i = strchr(ptr,RBP(wchar_t, &e->v_collect)[0]) - ptr;
                        switch (ch) {
                        case 'A': e->charset[i] = charset_uk; return;
                        case 'B': e->charset[i] = NULL; return;
                        case '0': e->charset[i] = charset_special; return;
                        case '1': e->charset[i] = NULL; return;
                        case '2': e->charset[i] = NULL; return;
                        default:
                                  fprintf(stderr, "unknown character set: %c\n", (int)ch);
                                  return;
                        }
                }
        }

        fprintf(stderr, "unknown ESC: ");
        for (int i = 0; i < RBL(wchar_t, &e->v_collect); ++i)
                fprintf(stderr, "%c", (char)RBP(wchar_t, &e->v_collect)[i]);
        fprintf(stderr, "%c\n", (unsigned)ch);
}

static void
print_cursor(FILE *f, struct cursor *c)
{
        fprintf(f, "  (%i,%i)\n", c->col, c->row);
        fprintf(f, "  mode_decom: %s\n",  c->mode_decom ? "true" : "false");
        fprintf(f, "  mode_decawm: %s\n", c->mode_decawm ? "true" : "false");
        fprintf(f, "  charsets: G%i G%i\n", c->charset, c->charset_save);
}

static char *
get_charset(wchar_t const *cs)
{
        if (!cs) return "US";
        if (cs == charset_uk) return "UK";
        if (cs == charset_special) return "Graphics";
        return "Unknown";
}

void
print_emulator_state(FILE *f, struct emulate *e)
{
        fprintf(f, "state=%i\n", e->state);
        fprintf(f, "cols=%i, rows=%i\n", e->cols, e->rows);
        fprintf(f, "v_param/v_collect: ");
        for (int i = 0; i < RBL(int, &e->v_param); ++i)
                fprintf(f, "%c%d", (i ? ' ' : '['), RBP(int, &e->v_param)[i]);
        fprintf(f, "] ");
        for (int i = 0; i < RBL(wchar_t, &e->v_collect); ++i)
                fprintf(f, "%c", (char)RBP(wchar_t, &e->v_collect)[i]);
        fprintf(f, "\n");
        fprintf(f, "scroll_begin=%i, scroll_end=%i\n", e->scroll_begin, e->scroll_end);
        fprintf(f, "mode_irm: %s\n", e->mode_irm ? "true" : "false");
        fprintf(f, "mode_kam: %s\n", e->mode_kam ? "true" : "false");
        fprintf(f, "mode_srm: %s\n", e->mode_srm ? "true" : "false");
        fprintf(f, "mode_lnm: %s\n", e->mode_lnm ? "true" : "false");
        fprintf(f, "mode_decckm: %s\n", e->mode_decckm ? "true" : "false");
        fprintf(f, "mode_decanm: %s\n", e->mode_decanm ? "true" : "false");
        fprintf(f, "mode_deckpam: %s\n", e->mode_deckpam ? "true" : "false");
        fprintf(f, "mode_dectcem: %s\n", e->mode_dectcem ? "true" : "false");
        fputs("Cursor:\n",f);
        print_cursor(f,&e->cursor);
        fputs("Saved cursor:\n",f);
        print_cursor(f,&e->saved);
        for(int i = 0; i < 4; i++)
                fprintf(f,"G%i: %s\n", i, get_charset(e->charset[i]));
        fputs("Tabs:",f);
        for(int i = 0; i < e->cols; i++)
                if(test_bit(e->tabs,i))
                        fprintf(f, " %i", i);
        fputc('\n', f);
        print_pty_state(f, e->pty);


}

/*

void emulate_dispatch_csi(wchar_t ch, Output * output) {
    using std::max;
    using std::min;

    int i;
    Style * s;
    bool flag;
    char pos_report[80];

    switch (ch) {
    case 'A': // CUU
        cursor.row = max(
            (cursor.row >= scroll_begin) ? int(scroll_begin) : 0,
            cursor.row - max(1, v_param[0]));
        return;

    case 'B': // CUD
        cursor.row = min(
            (cursor.row >= scroll_end) ? rows - 1 : scroll_end - 1,
            cursor.row + max(1, v_param[0]));
        return;

    case 'C': // CUF
        cursor.col = min(cols - 1, cursor.col + max(1, v_param[0]));
        return;

    case 'D': // CUB
        cursor.col = max(0, cursor.col - max(1, v_param[0]));
        return;

    case 'f': // HVP
    case 'H': // CUP
        while (v_param.size() < 2) v_param.push_back(0);
        cursor.row = max(0, min(rows - 1, max(1, v_param[0]) - 1));
        cursor.col = max(0, min(cols - 1, max(1, v_param[1]) - 1));
        if (cursor.mode_decom)
            cursor.row = std::max(0,
                         std::min(scroll_end - 1, scroll_begin + cursor.row));
        return;

    case 'J': // ED
        switch (v_param[0]) {
        default:
            i = cursor.row;
            if (cursor.col > 0) { dispatch_csi('K', output); ++i; }
            output->clear(0, i, cols, rows - i, blank_style);
            return;
        case 1:
            i = cursor.row;
            if (cursor.col < cols - 1) { dispatch_csi('K', output); --i; }
            output->clear(0, 0, cols, i + 1, blank_style);
            return;
        case 2:
            output->clear(0, 0, cols, rows, blank_style);
            return;
        }

    case 'K': // EL
        switch (v_param[0]) {
        default:
            output->clear(cursor.col, cursor.row, cols - cursor.col, 1, blank_style);
            return;
        case 1:
            output->clear(0, cursor.row, cursor.col + 1, 1, blank_style);
            return;
        case 2:
            output->clear(0, cursor.row, cols, 1, blank_style);
            return;
        }

    case 'L': // IL
        if (cursor.row < scroll_begin || cursor.row >= scroll_end) return;
        scroll(cursor.row, scroll_end, -max(1, v_param[0]), output);
        return;

    case 'M': // DL
        if (cursor.row < scroll_begin || cursor.row >= scroll_end) return;
        scroll(cursor.row, scroll_end, max(1, v_param[0]), output);
        return;

    case 'P': // DCH
        i = min(max(1, v_param[0]), cols - cursor.col);
        if (i <= 0) return;
        output->copy(cols - 1, cursor.row, cols - i, cursor.row, 1, 1);
        output->copy(
            cursor.col + i, cursor.row,
            cursor.col, cursor.row,
            cols - cursor.col - i, 1);
        output->clear(cols - i, cursor.row, i, 1, NULL);
        return;

    case 'c': // DA
        stuff(L"\e[?6c");
        return;

    case 'g': // TBC
        if (3 == v_param[0])
            std::fill(tabs.begin(), tabs.end(), false);
        else if (0 == v_param[0]
             &&  cursor.col >= 0 && cursor.col < int(tabs.size()))
            tabs[cursor.col] = false;
        return;

    case 'h': // SM
    case 'l': // RM
        flag = (ch == 'h');
        for (int i = 0; i < int(v_param.size()); ++i)
            if (v_collect.empty() || v_collect.front() != '?')
                switch (v_param[i]) {
                case 0: break;
                case 2: // KAM
                    mode_kam = flag;
                    break;
                case 4: // IRM
                    mode_irm = flag;
                    break;
                case 12: // SRM
                    mode_srm = flag;
                    break;
                case 20: // LNM
                    mode_lnm = flag;
                    break;
                default:
                    fprintf(stderr, "unknown mode: %d %s\n",
                            v_param[i], (flag ? "set" : "unset"));
                    break;
                }
            else
                switch (v_param[i]) {
                case 0: break;
                case 1: // DECCKM
                    mode_decckm = flag;
                    break;
                case 2: // DECANM
                    mode_decanm = flag;
                    break;
                case 3: break; // TODO: DECCOLM
                case 4: break; // DECSLM
                case 5: break; // TODO: DECSCNM
                case 6: // DECOM
                    cursor.mode_decom = flag;
                    cursor.col = 0;
                    cursor.row = flag ? int(scroll_begin) : 0;
                    break;
                case 7: // DECAWM
                    cursor.mode_decawm = flag;
                    break;
                case 8: break; // TODO: DECARM
                case 18: break; // TODO: DECPFF
                case 19: break; // TODO: DECPEX
                default:
                    fprintf(stderr, "unknown private mode: %d %s\n",
                            v_param[i], (flag ? "set" : "unset"));
                    break;
                }
        return;

    case 'm': // SGR
        s = new Style(*cursor.style);
        for (int i = 0; i < int(v_param.size()); ++i)
            switch (v_param[i]) {
            case 0: *s = Style(); break;
            case 1: s->is_bold = true; break;
            case 4: s->is_underline = true; break;
            case 5: s->is_blink = true; break;
            case 7: s->is_inverse = true; break;
            default:
                fprintf(stderr, "unknown text mode: %d\n", v_param[i]);
                break;
            }
        cursor.style = s;
        return;

    case 'n': // DSR
        switch (v_param.front()) {
        case 5:
            stuff(L"\e[0n");
            return;
        case 6:
            sprintf(pos_report, "\e[%d;%dR", cursor.row + 1, cursor.col + 1);
            stuff(pos_report);
            return;
        case 15:
            stuff(L"\e[?13n");
            return;
        default:
            fprintf(stderr, "unknown status report: %d\n", v_param[0]);
            return;
        }
        return;

    case 'r': // DECSTBM
        while (v_param.size() < 2) v_param.push_back(0);
        scroll_begin = max(0, min(rows - 1, v_param[0] - 1));
        scroll_end = max(
            int(scroll_begin + 1),
            v_param[1] ? min(int(rows), v_param[1]) : int(rows));
        cursor.col = 0;
        cursor.row = cursor.mode_decom ? int(scroll_begin) : 0;
        return;

    case 'y': // DECTST
        stuff(L"\e[0n");
        return;
    }

    fprintf(stderr, "unknown CSI: ");
    for (int i = 0; i < int(v_param.size()); ++i)
        fprintf(stderr, "%c%d", (i ? ' ' : '['), v_param[i]);
    fprintf(stderr, "] ");
    for (int i = 0; i < int(v_collect.size()); ++i)
        fprintf(stderr, "%c", v_collect[i]);
    fprintf(stderr, "%c\n", ch);
}

void emulate_dispatch_esc(wchar_t ch, Output * output) {
    using std::max;
    using std::min;

    int i, c, r;

    if (v_collect.empty()) {
        switch (ch) {
        case '<': // DECANM
            mode_decanm = true;
            return;
        case '=': // DECKPAM
            mode_deckpam = true;
            return;
        case '>': // DECKPNM
            mode_deckpam = false;
            mode_decckm = false;
            return;
        case '7': // DECSC
            saved = cursor;
            return;
        case '8': // DECRC
            cursor = saved;
            return;
        case 'A':
            if (cursor.row > 0 && cursor.row > scroll_begin)
                cursor.row = cursor.row - 1;
            return;
        case 'B':
            if (cursor.row < rows - 1 && cursor.row < scroll_end - 1)
                cursor.row = cursor.row + 1;
            return;
        case 'C':
            cursor.col = min(cols - 1, cursor.col + 1);
            return;
        case 'D':
            if (mode_decanm) { // IND
                if (cursor.row == scroll_end - 1)
                    scroll(scroll_begin, scroll_end, 1, output);
                else if (cursor.row < rows - 1)
                    cursor.row = cursor.row + 1;
                return;
            }
            cursor.col = max(0, cursor.col - 1);
            return;
        case 'E': // NEL
            cursor.col = 0;
            if (cursor.row == scroll_end - 1)
                scroll(scroll_begin, scroll_end, 1, output);
            else if (cursor.row < rows - 1)
                cursor.row = cursor.row + 1;
            return;
        case 'F':
            cursor.charset = cursor.charset_save = charset_special;
            return;
        case 'G':
            cursor.charset = cursor.charset_save = Null<wchar_t const *>();
            return;
        case 'H':
            if (!mode_decanm) {
                cursor.col = 0;
                cursor.row = (cursor.mode_decom) ? int(scroll_begin) : 0;
            }
            else if (cursor.col >= 0 && cursor.col < int(tabs.size())) // HTS
                tabs[cursor.col] = true;
            return;
        case 'J':
            i = cursor.row;
            if (cursor.col > 0) { dispatch_csi('K', output); ++i; }
            output->clear(0, i, cols, rows - i, blank_style);
            return;
        case 'K':
            output->clear(cursor.col, cursor.row, cols - cursor.col, 1, blank_style);
            return;
        case 'I':
        case 'M': // RI
            if (cursor.row == scroll_begin)
                scroll(scroll_begin, scroll_end, -1, output);
            else if (cursor.row > 0)
                cursor.row = cursor.row - 1;
            return;
        case 'N':
            cursor.charset = charset[2];
            return;
        case 'O':
            cursor.charset = charset[3];
            return;
        case 'Y':
            state = vt52_1_state;
            return;
        case 'Z': // DECID
            stuff(L"\e/Z");
            return;
        case 'c': // RIS
            c = cols; r = rows;
            *this = Emulator();
            set_size(c, r);
            return;
        }
    }
    else switch (v_collect.front()) {
    case '#':
        switch (ch) {
        case '8':
            v_chars.resize(cols);
            fill(v_chars.begin(), v_chars.end(), 'E');
            for (int r = 0; r < rows; ++r)
                output->set_text(0, r, cols, &v_chars[0], cursor.style);
            return;
        }
        break;

    case '(': // SCS
    case ')':
        i = ('(' == v_collect.front()) ? 0 : 1;
        switch (ch) {
        case 'A': charset[i] = charset_uk; return;
        case 'B': charset[i] = NULL; return;
        case '0': charset[i] = charset_special; return;
        case '1': charset[i] = NULL; return;
        case '2': charset[i] = NULL; return;
        default:
            fprintf(stderr, "unknown character set: %c\n", ch);
            return;
        }
    }

    fprintf(stderr, "unknown ESC: ");
    for (int i = 0; i < int(v_collect.size()); ++i)
        fprintf(stderr, "%c", v_collect[i]);
    fprintf(stderr, "%c\n", ch);
}

void emulate_stuff(wchar_t const * text) {
    int len = 0;
    while ('\0' != text[len]) ++len;
    v_reply.insert(v_reply.end(), text, text + len);
}

void emulate_stuff(char const * text) {
    v_reply.insert(v_reply.end(), text, text + strlen(text));
}

*/

/*

void emulate_event(XKeyEvent const &event, Output * output) {
    if (mode_kam || event.type != KeyPress) return;

    char buffer[1024];
    KeySym keysym;
    XKeyEvent tmp = event;
    int len = XLookupString(&tmp, buffer, sizeof(buffer) - 2, &keysym, NULL);
    if (mode_lnm && len > 0 && buffer[len - 1] == '\r')
        buffer[len++] = '\n';
    buffer[len++] = '\0';

    if (mode_decanm) // ANSI
        switch (keysym) {
        case XK_Up:    strcpy(buffer, mode_decckm ? "\eOA" : "\e[A"); break;
        case XK_Down:  strcpy(buffer, mode_decckm ? "\eOB" : "\e[B"); break;
        case XK_Right: strcpy(buffer, mode_decckm ? "\eOC" : "\e[C"); break;
        case XK_Left:  strcpy(buffer, mode_decckm ? "\eOD" : "\e[D"); break;
        case XK_KP_0:  if (mode_deckpam) strcpy(buffer, "\eOp"); break;
        case XK_KP_1:  if (mode_deckpam) strcpy(buffer, "\eOq"); break;
        case XK_KP_2:  if (mode_deckpam) strcpy(buffer, "\eOr"); break;
        case XK_KP_3:  if (mode_deckpam) strcpy(buffer, "\eOs"); break;
        case XK_KP_4:  if (mode_deckpam) strcpy(buffer, "\eOt"); break;
        case XK_KP_5:  if (mode_deckpam) strcpy(buffer, "\eOu"); break;
        case XK_KP_6:  if (mode_deckpam) strcpy(buffer, "\eOv"); break;
        case XK_KP_7:  if (mode_deckpam) strcpy(buffer, "\eOw"); break;
        case XK_KP_8:  if (mode_deckpam) strcpy(buffer, "\eOx"); break;
        case XK_KP_9:  if (mode_deckpam) strcpy(buffer, "\eOy"); break;
        case XK_KP_Separator: if (mode_deckpam) strcpy(buffer, "\eOl"); break;
        case XK_KP_Subtract:  if (mode_deckpam) strcpy(buffer, "\eOm"); break;
        case XK_KP_Decimal:   if (mode_deckpam) strcpy(buffer, "\eOn"); break;
        case XK_KP_Enter:     if (mode_deckpam) strcpy(buffer, "\eOM"); break;
        case XK_F1: strcpy(buffer, "\eOP"); break;
        case XK_F2: strcpy(buffer, "\eOQ"); break;
        case XK_F3: strcpy(buffer, "\eOR"); break;
        case XK_F4: strcpy(buffer, "\eOS"); break;
        }
    else // VT52
        switch (keysym) {
        case XK_Up:    strcpy(buffer, "\eA"); break;
        case XK_Down:  strcpy(buffer, "\eB"); break;
        case XK_Right: strcpy(buffer, "\eC"); break;
        case XK_Left:  strcpy(buffer, "\eD"); break;
        case XK_KP_0:  if (mode_deckpam) strcpy(buffer, "\e?p"); break;
        case XK_KP_1:  if (mode_deckpam) strcpy(buffer, "\e?q"); break;
        case XK_KP_2:  if (mode_deckpam) strcpy(buffer, "\e?r"); break;
        case XK_KP_3:  if (mode_deckpam) strcpy(buffer, "\e?s"); break;
        case XK_KP_4:  if (mode_deckpam) strcpy(buffer, "\e?t"); break;
        case XK_KP_5:  if (mode_deckpam) strcpy(buffer, "\e?u"); break;
        case XK_KP_6:  if (mode_deckpam) strcpy(buffer, "\e?v"); break;
        case XK_KP_7:  if (mode_deckpam) strcpy(buffer, "\e?w"); break;
        case XK_KP_8:  if (mode_deckpam) strcpy(buffer, "\e?x"); break;
        case XK_KP_9:  if (mode_deckpam) strcpy(buffer, "\e?y"); break;
        case XK_KP_Separator: if (mode_deckpam) strcpy(buffer, "\e?l"); break;
        case XK_KP_Subtract:  if (mode_deckpam) strcpy(buffer, "\e?m"); break;
        case XK_KP_Decimal:   if (mode_deckpam) strcpy(buffer, "\e?n"); break;
        case XK_KP_Enter:     if (mode_deckpam) strcpy(buffer, "\e?M"); break;
        case XK_F1: strcpy(buffer, "\eP"); break;
        case XK_F2: strcpy(buffer, "\eQ"); break;
        case XK_F3: strcpy(buffer, "\eR"); break;
        case XK_F4: strcpy(buffer, "\eS"); break;
        }

    if ('\0' != buffer[0]) {
        int const old = v_reply.size();
        stuff(buffer);
        if (!mode_srm)
            data(v_reply.size() - old, &v_reply[old], output);
    }
    else
        fprintf(stderr, "unknown keypress: %s\n", XKeysymToString(keysym));
}

int emulate_get_reply(wchar_t * buffer, int length) {
    length = std::min(length, int(v_reply.size()));
    copy(v_reply.begin(), v_reply.begin() + length, buffer);
    v_reply.erase(v_reply.begin(), v_reply.begin() + length);
    return length;
}

bool emulate_is_printable(wchar_t ch) {
    return ch >= 0x20 && ch < 0x7F || ch >= 0xA0;
}

void emulate_scroll(int begin, int end, int num, Output * output) {
    if (num > 0) {
        num = std::min(num, end - begin);
        output->copy(0, begin + num, 0, begin, cols, end - begin - num);
        output->clear(0, end - num, cols, num, blank_style);
    }
    else {
        num = std::min(-num, end - begin);
        output->copy(0, begin, 0, begin + num, cols, end - begin - num);
        output->clear(0, begin, cols, num, blank_style);
    }
}

*/
