#include <stdio.h>
#include <ctype.h>

// TO DO: determine screen size, handle scrolling if the output is normal
//        sequential lines of text.  Currently assumes wineconsole output
//        that fits on a screen.  Not sure if pseudo-scrolling by deleting
//        top line and inserting bottom line could happen.

#define ROWS 200
#define COLS 400

static char screen_buf[ROWS][COLS];
static int cur_row = 0;
static int cur_col = 0;
static int saved_row = 0;
static int saved_col = 0;
static int max_row_used = -1;
static int dirty = 0;

static void reset_screen(void) {
    int r, c;
    for (r = 0; r < ROWS; r++) {
        for (c = 0; c < COLS; c++) {
            screen_buf[r][c] = ' ';
        }
    }
    cur_row = 0;
    cur_col = 0;
    saved_row = 0;
    saved_col = 0;
    max_row_used = -1;
    dirty = 0;
}

static void clamp_cursor(void) {
    if (cur_row < 0) cur_row = 0;
    if (cur_row >= ROWS) cur_row = ROWS - 1;
    if (cur_col < 0) cur_col = 0;
    if (cur_col >= COLS) cur_col = COLS - 1;
}

static void mark_used(void) {
    if (cur_row > max_row_used) max_row_used = cur_row;
    dirty = 1;
}

static void put_char_at_cursor(int ch) {
    if (cur_row >= 0 && cur_row < ROWS && cur_col >= 0 && cur_col < COLS) {
        screen_buf[cur_row][cur_col] = (char)ch;
        mark_used();
    }
    if (cur_col < COLS - 1) cur_col++;
}

static void clear_to_eol(void) {
    int c;
    if (cur_row < 0 || cur_row >= ROWS) return;
    for (c = cur_col; c < COLS; c++) screen_buf[cur_row][c] = ' ';
}

static void clear_to_eos(void) {
    int r, c;
    if (cur_row < 0 || cur_row >= ROWS) return;
    clear_to_eol();
    for (r = cur_row + 1; r < ROWS; r++) {
        for (c = 0; c < COLS; c++) {
            screen_buf[r][c] = ' ';
        }
    }
}

static void clear_line(void) {
    int c;
    if (cur_row < 0 || cur_row >= ROWS) return;
    for (c = 0; c < COLS; c++) screen_buf[cur_row][c] = ' ';
}

static void flush_screen(void) {
    int r, c, last_row = -1;

    if (!dirty) {
        reset_screen();
        return;
    }

    for (r = 0; r <= max_row_used && r < ROWS; r++) {
        int last_nonspace = -1;
        for (c = COLS - 1; c >= 0; c--) {
            if (screen_buf[r][c] != ' ') {
                last_nonspace = c;
                break;
            }
        }
        if (last_nonspace >= 0) last_row = r;
    }

    if (last_row >= 0) {
        for (r = 0; r <= last_row; r++) {
            int last_nonspace = -1;
            for (c = COLS - 1; c >= 0; c--) {
                if (screen_buf[r][c] != ' ') {
                    last_nonspace = c;
                    break;
                }
            }
            if (last_nonspace >= 0) {
                fwrite(screen_buf[r], 1, (size_t)(last_nonspace + 1), stdout);
            }
            if (r != last_row) fputc('\n', stdout);
        }
        fflush(stdout);
    }

    reset_screen();
}

static int read_number(const char **pp, int *present) {
    int v = 0;
    const char *p = *pp;
    *present = 0;

    while (isdigit((unsigned char)*p)) {
        *present = 1;
        v = v * 10 + (*p - '0');
        p++;
    }
    *pp = p;
    return v;
}

static void handle_csi(const char **pp) {
    const char *p = *pp;
    int params[16];
    int count = 0;
    int have;
    char final;

    while (*p == '?' || *p == '>' || *p == '!') p++;

    for (;;) {
        if (count < 16) {
            params[count++] = read_number(&p, &have);
        } else {
            int dummy_present;
            (void)read_number(&p, &dummy_present);
        }
        if (*p == ';') {
            p++;
            continue;
        }
        break;
    }

    final = *p;
    if (final == '\0') {
        *pp = p;
        return;
    }
    p++;

    switch (final) {
        case 'H':
        case 'f': {
            int row = (count >= 1 && params[0] > 0) ? params[0] : 1;
            int col = (count >= 2 && params[1] > 0) ? params[1] : 1;
            cur_row = row - 1;
            cur_col = col - 1;
            clamp_cursor();
            break;
        }

        case 'A': {
            int n = (count >= 1 && params[0] > 0) ? params[0] : 1;
            cur_row -= n;
            clamp_cursor();
            break;
        }

        case 'B': {
            int n = (count >= 1 && params[0] > 0) ? params[0] : 1;
            cur_row += n;
            clamp_cursor();
            break;
        }

        case 'C': {
            int n = (count >= 1 && params[0] > 0) ? params[0] : 1;
            cur_col += n;
            clamp_cursor();
            break;
        }

        case 'D': {
            int n = (count >= 1 && params[0] > 0) ? params[0] : 1;
            cur_col -= n;
            clamp_cursor();
            break;
        }

        case 'G': {
            int col = (count >= 1 && params[0] > 0) ? params[0] : 1;
            cur_col = col - 1;
            clamp_cursor();
            break;
        }

        case 'd': {
            int row = (count >= 1 && params[0] > 0) ? params[0] : 1;
            cur_row = row - 1;
            clamp_cursor();
            break;
        }

        case 'J': {
            int mode = (count >= 1) ? params[0] : 0;
            if (mode == 2 || mode == 3) {
                flush_screen();
            } else if (mode == 0) {
                clear_to_eos();
            }
            break;
        }

        case 'K': {
            int mode = (count >= 1) ? params[0] : 0;
            if (mode == 0 || mode == 2) clear_line();
            break;
        }

        case 's':
            saved_row = cur_row;
            saved_col = cur_col;
            break;

        case 'u':
            cur_row = saved_row;
            cur_col = saved_col;
            clamp_cursor();
            break;

        case 'm':
        default:
            break;
    }

    *pp = p;
}

static void process_stream(void) {
    int ch;
    enum { ST_TEXT, ST_ESC, ST_CSI } state = ST_TEXT;
    char escbuf[256];
    size_t escpos = 0;

    reset_screen();

    while ((ch = fgetc(stdin)) != EOF) {
        switch (state) {
            case ST_TEXT:
                if (ch == 27) {
                    state = ST_ESC;
                    escpos = 0;
                } else if (ch == 12) {
                    flush_screen();
                } else if (ch == '\r') {
                    cur_col = 0;
                } else if (ch == '\n') {
                    if (cur_row < ROWS - 1) cur_row++;
                } else if (ch == '\b') {
                    if (cur_col > 0) cur_col--;
                } else if (ch == '\t') {
                    int next = ((cur_col / 8) + 1) * 8;
                    cur_col = next < COLS ? next : COLS - 1;
                } else if (isprint((unsigned char)ch)) {
                    put_char_at_cursor(ch);
                }
                break;

            case ST_ESC:
                if (ch == '[') {
                    state = ST_CSI;
                    escpos = 0;
                } else if (ch == 'c') {
                    flush_screen();
                    state = ST_TEXT;
                } else if (ch == '7') {
                    saved_row = cur_row;
                    saved_col = cur_col;
                    state = ST_TEXT;
                } else if (ch == '8') {
                    cur_row = saved_row;
                    cur_col = saved_col;
                    clamp_cursor();
                    state = ST_TEXT;
                } else {
                    state = ST_TEXT;
                }
                break;

            case ST_CSI:
                if (escpos + 1 < sizeof(escbuf)) escbuf[escpos++] = (char)ch;
                if (ch >= '@' && ch <= '~') {
                    escbuf[escpos] = '\0';
                    {
                        const char *p = escbuf;
                        handle_csi(&p);
                    }
                    state = ST_TEXT;
                }
                break;
        }
    }

    flush_screen();
}

int main(void) {
    process_stream();
    putchar(10);    
    return ferror(stdin) ? 1 : 0;
}
