#include <stdlib.h>
#include <string.h>
#include <string>
#include <unistd.h>
#include <signal.h>
#include <pty.h>
#include <sys/time.h>
#include <sys/types.h>
#include <SDL/SDL.h>
#include "6x10.h"
using namespace std;

#define FONT_WIDTH 6
#define FONT_HEIGHT 10
#define SCREEN_WIDTH (320/FONT_WIDTH)
#define SCREEN_HEIGHT (240/FONT_HEIGHT)

#define GP2X_BUTTON_UP              0
#define GP2X_BUTTON_DOWN            4
#define GP2X_BUTTON_LEFT            2
#define GP2X_BUTTON_RIGHT           6
#define GP2X_BUTTON_UPLEFT          1
#define GP2X_BUTTON_UPRIGHT         7
#define GP2X_BUTTON_DOWNLEFT        3
#define GP2X_BUTTON_DOWNRIGHT       5
#define GP2X_BUTTON_CLICK           18
#define GP2X_BUTTON_A               12
#define GP2X_BUTTON_B               13
#define GP2X_BUTTON_X               15
#define GP2X_BUTTON_Y               14
#define GP2X_BUTTON_L               10
#define GP2X_BUTTON_R               11
#define GP2X_BUTTON_START           8
#define GP2X_BUTTON_SELECT          9
#define GP2X_BUTTON_VOLUP           16
#define GP2X_BUTTON_VOLDOWN         17

SDL_Surface *screen;
SDL_Joystick *joystick;
int master;	// pty master fd
bool debug, capture;

static void exit_func()
{
    close(master);
#ifdef __arm__
    fflush(stdout);
    fflush(stderr);
    sync();
    chdir("/usr/gp2x");
    execl("/usr/gp2x/gp2xmenu","/usr/gp2x/gp2xmenu", NULL);
#endif
}

static void init()
{
    debug = getenv("STERM_DEBUG");
    capture = getenv("STERM_CAPTURE");

    atexit(exit_func);

    if (SDL_Init(SDL_INIT_VIDEO | SDL_INIT_JOYSTICK) < 0)
    {
        fprintf(stderr, "Unable to init SDL: %s\n", SDL_GetError());
        exit(1);
    }
    atexit(SDL_Quit);

    SDL_ShowCursor(0);
    joystick = SDL_JoystickOpen(0);
    SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);

    screen = SDL_SetVideoMode(320, 240, 16, SDL_SWSURFACE);
    if (screen == NULL)
    {
        fprintf(stderr, "Unable to set 320x240 video: %s\n", SDL_GetError());
        exit(1);
    }

    struct winsize winsize;
    winsize.ws_row = SCREEN_HEIGHT-1;
    winsize.ws_col = SCREEN_WIDTH;
    winsize.ws_xpixel = 0;
    winsize.ws_ypixel = 0;
    signal(SIGCHLD,SIG_IGN);	// XXX: should use sigaction for portability
    pid_t pid = forkpty(&master, NULL, NULL, &winsize);
    if(pid == -1)
    {
        perror("Unable to forkpty");
        exit(1);
    }

    if(pid == 0)	// child
    {
        execl("/bin/sh", "/bin/sh", NULL);
        perror("Unable to execl\n");
        exit(1);
    }
}

static void lock_surface()
{
    if(SDL_MUSTLOCK(screen))
    {
        if ( SDL_LockSurface(screen) < 0 ) {
            fprintf(stderr, "Unable to lock_surface\n");
            exit(1);
        }
    }
}

static void unlock_surface()
{
    if (SDL_MUSTLOCK(screen))
    {
        SDL_UnlockSurface(screen);
    }
}

static void show_cursor();
static void hide_cursor();

/* screen position */

int pos;
int bg_color = 0;
int fg_color = 0xffff;

static void clear_char(int row, int col);
void pos_adj(int delta)
{
    pos += delta;
    int row = pos/SCREEN_WIDTH;
    if(row >= SCREEN_HEIGHT-1)	// last line (SCREEN_HEIGHT-1) is for selector
    {
        hide_cursor();

        // scroll up N lines
        int N = row - (SCREEN_HEIGHT-1) + 1;
        pos -= N * SCREEN_WIDTH;

        if(N > SCREEN_HEIGHT-1) N = SCREEN_HEIGHT - 1;
        lock_surface();
        
        char *pixels = (char *)screen->pixels;
        int pitch = screen->pitch;
        memmove(pixels, 
                pixels + pitch * N * FONT_HEIGHT,
                pitch * (SCREEN_HEIGHT-1-N) * FONT_HEIGHT);
        // clear new lines
        memset(pixels + pitch * (SCREEN_HEIGHT-1-N) * FONT_HEIGHT,
                0, N * pitch * FONT_HEIGHT);

        unlock_surface();
        SDL_UpdateRect(screen, 0, 0, 320, 240-FONT_HEIGHT);

        //show_cursor();
    }
    if(pos < 0)
        pos = 0;
}

/* cursor */

static void mark_pos(int p)
{
    int row = p / SCREEN_WIDTH;
    int col = p % SCREEN_WIDTH;

    lock_surface();
        
    char *pixels = (char *)screen->pixels;
    int pitch = screen->pitch;
    int x = FONT_WIDTH * col;
    int y = row * FONT_HEIGHT + FONT_HEIGHT - 1;
    short *base = (short *)(pixels + pitch * y + x * 2);
    for(int i=0;i<FONT_WIDTH;i++)
        base[i] ^= 0xffff;

    unlock_surface();
    SDL_UpdateRect(screen, x, y, FONT_WIDTH, 1);
}

int last_pos = -1;	// last shown cursor position

static void show_cursor()
{
    if(last_pos == pos) return;
    if(last_pos >= 0)
    {
        mark_pos(last_pos);
    }
    mark_pos(pos);
    last_pos = pos;
}

static void hide_cursor()
{
    if(last_pos >= 0)
    {
        mark_pos(last_pos);
        last_pos = -1;
    }
}

/* draw char */

static void draw_char(int row, int col, unsigned char c, bool inverse=false)
{
    if(row * SCREEN_WIDTH + col == last_pos) hide_cursor();
    lock_surface();
    int y = row * FONT_HEIGHT;
    int x = col * FONT_WIDTH;
    for(int j=0; j<FONT_HEIGHT; j++)
    {
      unsigned char v = font[c][j];
      for(int k=0;k<FONT_WIDTH;k++)
      {
          *(short *)((char *)screen->pixels + screen->pitch*(y+j) + (x+k)*2) = 
            (v&0x80)?
            (inverse?bg_color:fg_color):
            (inverse?fg_color:bg_color);
          v <<= 1;
      }
    }
    unlock_surface();
    SDL_UpdateRect(screen, x, y, FONT_WIDTH, FONT_HEIGHT);
    //if(row * SCREEN_WIDTH + col == last_pos) show_cursor();
}

static void draw_char(unsigned char c, bool inverse=false)
{
    int row = pos / SCREEN_WIDTH;
    int col = pos % SCREEN_WIDTH;
    draw_char(row, col, c, inverse);
}

static void clear_char(int row, int col)
{
    draw_char(row, col, ' ', false);
}

/* character selector */

const char* table = "0123456789abcdefghijklmnopqrstuvwxyz "
                    "./-*?<>|:;,_'\"!&()[]{}\\+=#$%^`~@";
int table_N;
int table_start;	// display start position
int table_pos;		// current selected position

void show_selector()
{
    for(int i=0;i<SCREEN_WIDTH;i++)
    {
        int to_show = (table_start + i) % table_N;	// the character to show
        bool inverse = (table_pos == to_show);
        draw_char(SCREEN_HEIGHT-1, i, table[to_show], inverse);
    }    
}

void selector_left()
{
    if(table_pos > 0)
    {
        table_pos--;
        if(table_start > table_pos)
            table_start = table_pos;
        show_selector();
    }
}

void selector_right()
{
    if(table_pos < table_N - 1)
    {
        table_pos++;
        if(table_start < table_pos - SCREEN_WIDTH + 1)
            table_start = table_pos - SCREEN_WIDTH + 1;
        show_selector();
    }
}

char get_selected()
{
    return table[table_pos];
}

void init_selector()
{
    table_N = strlen(table);
    table_start = 0;
    table_pos = 0;

    show_selector();
}

string send_buf;	// the buffer we are sending to the pty

void process_SDL_Event()
{
    SDL_Event event;
    bool no_event = true;
    static bool ctrl_down = false;
    static bool shift_down = false;
    static bool js_down = false;
    while (SDL_PollEvent(&event))
    {
        no_event = false;
        switch (event.type)
        {
            case SDL_KEYDOWN:
                switch(event.key.keysym.sym)
                {
                    case SDLK_RIGHT:
right:              	selector_right();
                        break;
                    case SDLK_LEFT:
left:               	selector_left();
                        break;
                    case SDLK_UP:
up:                     send_buf += "\x1b[A";
                        break;
                    case SDLK_DOWN:
down:                   send_buf += "\x1b[B";
                        break;
                    case SDLK_SPACE:
space:              	{
                            char c = get_selected();
                            if(ctrl_down && c >= 'a' && c <= 'z')
                                c = c - 'a' + 1;	// ^A - ^Z
                            else if(shift_down && c >= 'a' && c <= 'z')
                                c = c - 'a' + 'A';	// A - Z
                            send_buf += c;
                        }
                        break;
                    case SDLK_BACKSPACE:
back:               	send_buf += '\177';
                        break;
                    case SDLK_RETURN:
enter:              	send_buf.append("\r");
                        break;
                    case SDLK_LCTRL:
ctrl:                   ctrl_down = true;
                        break;
                    case SDLK_LSHIFT:
shift:                  shift_down = true;
                        break;
                    case SDLK_RSHIFT:
js:                     js_down = true;
                        break;
                    case SDLK_ESCAPE:
esc:                    send_buf += char(0x1b);
                        break;
                    case SDLK_TAB:
tab:                    send_buf += '\t';
                        break;
                }
	            break;
            case SDL_KEYUP:
                switch(event.key.keysym.sym)
                {
                    case SDLK_LCTRL:
ctrl_up:                ctrl_down = false;
                        break;
                    case SDLK_LSHIFT:
shift_up:               shift_down = false;
                        break;
                    case SDLK_RSHIFT:
js_up:                  js_down = false;
                        break;
                }
                break;
            case SDL_JOYBUTTONDOWN:
				switch(event.jbutton.button)
	            {
                    case GP2X_BUTTON_RIGHT:
		    			goto right;
                    case GP2X_BUTTON_LEFT:
		    			goto left;
                    case GP2X_BUTTON_UP:
                        goto up;
                    case GP2X_BUTTON_DOWN:
                        goto down;
                    case GP2X_BUTTON_A:
                        goto space;
                    case GP2X_BUTTON_B:
                        goto enter;
                    case GP2X_BUTTON_START:
                        goto quit;
                    case GP2X_BUTTON_L:
                        goto esc;
                    case GP2X_BUTTON_R:
                        goto back;
                    case GP2X_BUTTON_VOLDOWN:
                        goto ctrl;
                    case GP2X_BUTTON_VOLUP:
                        goto shift;
                    case GP2X_BUTTON_SELECT:
                        goto js;
                    case GP2X_BUTTON_CLICK:
                        goto tab;
                    case GP2X_BUTTON_X:
                        if(capture)
                        {
                            // screenshot
                            lock_surface();
                            char *pixels = (char *)screen->pixels;
                            int pitch = screen->pitch;
                            FILE *fp = fopen("/mnt/sd/1.ppm", "wb");
                            if(fp != NULL)
                            {
                                fprintf(fp,"P6\n320 240\n63\n");
                                for(int i=0;i<240;i++)
                                {
                                    unsigned short* line = (unsigned short *)(pixels + pitch*i);
                                    for(int j=0;j<320;j++)
                                    {
                                        unsigned short v = line[j];
                                        fprintf(fp, "%c%c%c", (v>>11)*2, 
                                            (v>>5)&0x3f, (v&0x1f)*2);
                                    }
                                }
                                fclose(fp);
                            }
                            unlock_surface();
                        }
			else {
                          send_buf.append(" ");
                        }
            	}
                break;
            case SDL_JOYBUTTONUP:
				switch(event.jbutton.button)
				{
                    case GP2X_BUTTON_VOLDOWN:
                        goto ctrl_up;
                    case GP2X_BUTTON_VOLUP:
                        goto shift_up;
                    case GP2X_BUTTON_SELECT:
                        goto js_up;
				}
				break;
            case SDL_QUIT:
quit:           exit(0);
        }
    }
    if(no_event)	// check for repeat
    {
        bool jr = SDL_JoystickGetButton(joystick, GP2X_BUTTON_RIGHT);
        bool jl = SDL_JoystickGetButton(joystick, GP2X_BUTTON_LEFT);
        bool ju = SDL_JoystickGetButton(joystick, GP2X_BUTTON_UP);
        bool jd = SDL_JoystickGetButton(joystick, GP2X_BUTTON_DOWN);

        if(ju) { send_buf += "\x1b[A"; }
        if(jd) { send_buf += "\x1b[B"; }
        if(jr) { if(js_down) send_buf += "\x1b[C"; else selector_right(); }
        if(jl) { if(js_down) send_buf += "\x1b[D"; else selector_left(); }
    }
}

/* ESC commands */
bool blinking_mode;
bool halfbright_mode;
bool bold_mode;
bool reverse_mode;
bool dark_mode;

void cmd_up()	// Cursor up one line
{
    pos_adj(-SCREEN_WIDTH);
}

void cmd_do()	// Cursor down one line
{
    pos_adj(SCREEN_WIDTH);
}

void cmd_nd()	// Cursor right one character
{
    pos_adj(1);
}

void cmd_cr()	// Carriage return
{
    pos = pos/SCREEN_WIDTH*SCREEN_WIDTH;
}

void cmd_ta()	// move to next hardware tab 
{                                
    int col = pos % SCREEN_WIDTH;
    pos_adj((col+7)/8*8-col);
}

void cmd_le()	// Cursor left one character
{
    pos_adj(-1);
}

void cmd_ho()	// Cursor home
{
    pos = 0;
}

void cmd_cd()	// Clear to end of screen
{
    int row = pos / SCREEN_WIDTH;
    int col = pos % SCREEN_WIDTH;
    for(int i=col;i<SCREEN_WIDTH;i++)	// clear to the end of line
        clear_char(row, i);
    for(int i=row+1;i<SCREEN_HEIGHT-1;i++)	// clear other lines
    for(int j=0;j<SCREEN_WIDTH;j++)
        clear_char(i,j);
}

void cmd_ce()	// Clear to end of line
{
    int row = pos / SCREEN_WIDTH;
    int col = pos % SCREEN_WIDTH;
    for(int i=col;i<SCREEN_WIDTH;i++)
        clear_char(row, i);
}

void cmd_se()	// End standout mode
{
}

void cmd_cs(int arg1, int arg2)	// Scroll region from line %1 to %2
{
}

void cmd_mb() { blinking_mode = true; }
void cmd_mh() { halfbright_mode = true; }
void cmd_md() { bold_mode = true; }
void cmd_mr() { reverse_mode = true; }
void cmd_mk() { dark_mode = true; }

void cmd_DO(int arg1)	// Cursor down #1 lines
{
    pos_adj(SCREEN_WIDTH * arg1);
}

void cmd_UP(int arg1)	// Cursor up %1 lines
{
    pos_adj(-SCREEN_WIDTH * arg1);
}

void cmd_LE(int arg1)	// Cursor left %1 characters
{
    pos_adj(-arg1);
}

void cmd_RI(int arg1)	// Cursor right %1 characters
{
    pos_adj(arg1);
}

void cmd_cm(int arg1, int arg2)	// Cursor move to row %1 and column %2 (on screen)
{
    pos = (arg1 - 1) * SCREEN_WIDTH + (arg2 - 1);
    pos_adj(0);
}

int saved_cursor_position;
void cmd_sc()	// Save cursor position
{
    saved_cursor_position = pos;
}

void cmd_rc()	// Restore saved cursor position
{
    pos = saved_cursor_position;
}

void cmd_ch(int arg1)	// Move cursor horizontally only to column %1
{
    pos = pos/SCREEN_WIDTH * SCREEN_WIDTH + (arg1-1);
    pos_adj(0);
}

void cmd_ks()	// Turn keypad on
{
}

void cmd_us()	// Start underlining
{
}

void cmd_ue()	// End underlining
{
}

void cmd_cv(int arg1)	// Move cursor vertically only to line %1
{
    int col = pos % SCREEN_WIDTH;
    pos = (arg1 - 1) * SCREEN_WIDTH + col;
    pos_adj(0);
}

void cmd_al()	// Insert one line
{
    int row = pos / SCREEN_WIDTH;

    hide_cursor();

    lock_surface();
        
    char *pixels = (char *)screen->pixels;
    int pitch = screen->pitch;
    memmove(pixels + pitch * (row+1) * FONT_HEIGHT, 
            pixels + pitch * row * FONT_HEIGHT,
            pitch * (SCREEN_HEIGHT-2-row) * FONT_HEIGHT);
    // clear the new line
    memset(pixels + pitch * row * FONT_HEIGHT, 
            0, screen->pitch * FONT_HEIGHT);

    unlock_surface();
    SDL_UpdateRect(screen, 0, row*FONT_HEIGHT, 
                         320, (SCREEN_HEIGHT-1-row)*FONT_HEIGHT);

    //show_cursor();
}

void cmd_DC(int arg1) // Delete %1 characters
{
    hide_cursor();
    lock_surface();
    int row = pos/SCREEN_WIDTH;
    int col = pos%SCREEN_WIDTH;
    if(arg1 > SCREEN_WIDTH - col) arg1 = SCREEN_WIDTH - col;
    char *pixels = (char *)screen->pixels;
    int pitch = screen->pitch;
    
    // move from col+arg1 to col, length SCREEN_WIDTH-col-arg1
    int x0 = FONT_WIDTH * col;
    int x1 = FONT_WIDTH * (col + arg1);
    int len = FONT_WIDTH * (SCREEN_WIDTH-col-arg1);
    int y = FONT_HEIGHT * row;
    for(int i=0;i<FONT_HEIGHT;i++)
    {
        memmove(pixels + pitch * (y+i) + x0*2, 
                pixels + pitch * (y+i) + x1*2,
                len*2);
        // clear new space
        memset(pixels + pitch * (y+i) + 
                ((SCREEN_WIDTH-arg1)*FONT_WIDTH)*2, 0, arg1*FONT_WIDTH*2);
    }
    unlock_surface();
    SDL_UpdateRect(screen, x0, row*FONT_HEIGHT, 
                           320-x0, FONT_HEIGHT);
    //show_cursor();
}

void cmd_IC(int arg1) // Insert %1 characters
{
    hide_cursor();
    lock_surface();
    int row = pos/SCREEN_WIDTH;
    int col = pos%SCREEN_WIDTH;
    if(arg1 > SCREEN_WIDTH - col) arg1 = SCREEN_WIDTH - col;
    char *pixels = (char *)screen->pixels;
    int pitch = screen->pitch;
    
    // move from col to col+arg1, length SCREEN_WIDTH-col-arg1
    int x0 = FONT_WIDTH * col;
    int x1 = FONT_WIDTH * (col + arg1);
    int len = FONT_WIDTH * (SCREEN_WIDTH-col-arg1);
    int y = FONT_HEIGHT * row;
    for(int i=0;i<FONT_HEIGHT;i++)
    {
        memmove(pixels + pitch * (y+i) + x1*2, 
                pixels + pitch * (y+i) + x0*2,
                len*2);
        // clear new space
        memset(pixels + pitch * (y+i) + 
                (col*FONT_WIDTH)*2, 0, arg1*FONT_WIDTH*2);
    }
    unlock_surface();
    SDL_UpdateRect(screen, x0, row*FONT_HEIGHT, 
                           320-x0, FONT_HEIGHT);
    //show_cursor();
}

void cmd_ei()	// End insert mode
{
}

void cmd_im()	// Begin insert mode
{
}

void cmd_S2()	//?
{
}

void cmd_S3()	// ?
{
}

/* color handling */

const int R = 0xf800;
const int G = 0x07e0;
const int B = 0x001f;
int colors[] = { 0, R, G, R|G, B, R|B, G|B, R|G|B };

void cmd_color(int a)
{
    if(a >= 30 && a <= 37)
        fg_color = colors[a-30];
    else if(a >= 40 && a <= 47)
        bg_color = colors[a-40];
    else if(a == 39)
        fg_color = colors[7];
    else if(a == 49)
        bg_color = colors[0];
    else if(a == 0)
    {
        // End all mode like so, us, mb, md and mr
        fg_color = colors[7];
        bg_color = colors[0];
        blinking_mode = false;
        halfbright_mode = false;
        bold_mode = false;
        reverse_mode = false;
    }
}

enum {
    INITIAL,
    LBRACKET,
    IN_ARG1,
    IN_ARG2,
    LBRACKET_Q,
    IN_ARG1_Q
};
// return true if still in esc mode
bool process_esc_mode(unsigned char c)
{
    static int state = INITIAL;
    static int arg1 = 0, arg2 = 0;

/*
    if(c == 0x1b)
    {
        printf("error -- resync\n");
        state = INITIAL;
        return true;
    }
*/
    
    switch(state)
    {
        case INITIAL:
            if(c == '[') { state = LBRACKET; return true; }
            if(c == '7') { cmd_sc(); goto done; }
            if(c == '8') { cmd_rc(); goto done; }
            break;
        case LBRACKET:
            if(c >= '0' && c <= '9') 
            {
                state = IN_ARG1; arg1=c-'0'; return true;
            }
            if(c == 'A') { cmd_up(); goto done; }
            if(c == 'B') { cmd_do(); goto done; }
            if(c == 'C') { cmd_nd(); goto done; }
            if(c == 'H') { cmd_ho(); goto done; }
            if(c == 'K') { cmd_ce(); goto done; }
            if(c == 'J') { cmd_cd(); goto done; }
            if(c == 'L') { cmd_al(); goto done; }
            if(c == 'P') { cmd_DC(1); goto done; }
            if(c == '@') { cmd_IC(1); goto done; }
            if(c == 'm') { cmd_se(); goto done; }
            if(c == '?') { state = LBRACKET_Q; return true; }
            break;
        case LBRACKET_Q:
            if(c >= '0' && c <= '9') 
            {
                state = IN_ARG1_Q; arg1=c-'0'; return true;
            }
            break;
        case IN_ARG1_Q:
            if(c >= '0' && c <= '9')
            {
                arg1 = arg1*10+(c-'0'); return true;
            }
            else if(c == 'h')
            {
                if(arg1 == 1) { cmd_ks(); goto done; }
                if(arg1 == 25) { 
                    // :ve=\E[?25h\E[?0c
                    // :vi=\E[?25l\E[?1c
                    // :vs=\E[?25h\E[?8c
                    goto done;
                }
            }
            else if(c == 'l')
            {
                if(arg1 == 25)
                    goto done;
            }
            else if(c == 'c')
            {
                if(arg1 == 0 || arg1 == 1 || arg1 == 8)
                    goto done;
            }
            break;
        case IN_ARG1:
            if(c >= '0' && c <= '9')
            {
                arg1 = arg1*10+(c-'0'); return true;
            }
            else if(c == 'm')
            {
                if(arg1 == 5) { cmd_mb(); goto done; }
                if(arg1 == 2) { cmd_mh(); goto done; }
                if(arg1 == 1) { cmd_md(); goto done; }
                if(arg1 == 7) { cmd_mr(); goto done; }
                if(arg1 == 8) { cmd_mk(); goto done; }
                if(arg1 == 4) { cmd_us(); goto done; }
                if(arg1 == 11) { cmd_S2(); goto done; }
                if(arg1 == 10) { cmd_S3(); goto done; }
                if(arg1 == 24) { cmd_ue(); goto done; }
                if(arg1 >= 30 && arg1 <= 49 || arg1 == 0)
                {
                    cmd_color(arg1); goto done;
                }
            }
            else if(c == 'A')
            {
                cmd_UP(arg1); goto done;
            }
            else if(c == 'B')
            {
                cmd_DO(arg1); goto done;
            }
            else if(c == 'C')
            {
                cmd_RI(arg1); goto done;
            }
            else if(c == 'D')
            {
                cmd_LE(arg1); goto done;
            }
            else if(c == 'G')
            {
                cmd_ch(arg1); goto done;
            }
            else if(c == 'J')
            {
                if(arg1 == 2 || arg1 == 0) { cmd_cd(); goto done; }
            }
            else if(c == 'K')
            {
                if(arg1 == 0) { cmd_ce(); goto done; }
            }
            else if(c == 'P')
            {
                cmd_DC(arg1); goto done;
            }
            else if(c == '@')
            {
                cmd_IC(arg1); goto done;
            }
            else if(c == ';')
            {
                arg2 = 0; state = IN_ARG2; return true;
            }
            else if(c == 'd')
            {
                cmd_cv(arg1); goto done;
            }            
            else if(c == 'l')
            {
                if(arg1 == 4) { cmd_ei(); goto done; }
            }
            else if(c == 'h')
            {
                if(arg1 == 4) { cmd_im(); goto done; }
            }
            break;
        case IN_ARG2:
            if(c >= '0' && c <= '9')
            {
                arg2 = arg2*10+(c-'0'); return true;
            }
            else if(c == 'r') 
            {
                cmd_cs(arg1, arg2); goto done;
            }
            else if(c == 'H')
            {
                cmd_cm(arg1, arg2); goto done;
            }
            else if(c == 'm')
            {
                cmd_color(arg1);
                cmd_color(arg2);
                goto done;
            }
            break;
    }
    return false;
done:
    if(debug) printf("------------------------------------------\n");
    state = INITIAL;
    return false;
}

int main(int argc, char **argv)
{
    init();
    init_selector();
    bool esc_mode = false;
    
    struct timeval start_time;
    gettimeofday(&start_time, NULL);
    
    while(1)
    {
        // poll SDL event every 75 ms
        struct timeval tv;
        gettimeofday(&tv, NULL);	// tv = current time
        
        if((tv.tv_sec - start_time.tv_sec) * 1000000 +
           (tv.tv_usec - start_time.tv_usec) > 75000)
        {
            process_SDL_Event();
            start_time = tv;
        }

        // calculate remaining time for select()
        tv.tv_sec = start_time.tv_sec - tv.tv_sec;
        tv.tv_usec = start_time.tv_usec + 75000 - tv.tv_usec;

        if(tv.tv_usec >= 1000000)
        {
            ++tv.tv_sec;
            tv.tv_usec -= 1000000;
        }
        else if(tv.tv_usec < 0)
        {
            --tv.tv_sec;
            tv.tv_usec += 1000000;
        }

        if(tv.tv_sec < 0)
        {
            // time goes backward
            gettimeofday(&start_time, NULL);
            continue;
        }

        show_cursor();

        fd_set rfds, wfds;
        FD_ZERO(&rfds);
        FD_ZERO(&wfds);
        FD_SET(master, &rfds);	// always check input
        if(!send_buf.empty())	// check output if there is something to send
            FD_SET(master, &wfds);

        int retval = select(master+1, &rfds, &wfds, NULL, &tv);
        if(retval == -1)
        {
            perror("select()");
            sleep(1);
        }    
        else if(retval)
        {
            if(FD_ISSET(master, &rfds))
            {
                char buf[512];
                int n = read(master, buf, 512);
                if(n > 0)
                {
                    for(int i=0;i<n;i++)
                    {
                        unsigned char c = buf[i];
                        if(debug) printf("read: 0x%x(%c)\n", c, c);

                        if(esc_mode)
                        {
                            esc_mode = process_esc_mode(c);
                            continue;
                        }
                        
                        switch(c)
                        {
                            case '\n':	// ^J
                                cmd_do();
                                break;
                            case '\r':	// ^M
                                cmd_cr();
                                break;
                            case '\t':	// ^I
                                cmd_ta();
                                break;
                            case '\b':	// ^H
                                cmd_le();
                                break;
                            case '\a':	// ^G
                                break;
                            case 0x1b:	// ESC
                                esc_mode = true;
                                break;
                            default:
                                draw_char(c, reverse_mode);
                                pos_adj(+1);
                                break;
                        }
                    }
                }
            }
            else if(FD_ISSET(master,&wfds))
            {
                int n = write(master, send_buf.data(), send_buf.size());
                if(n > 0)
                {
                    send_buf = send_buf.substr(n);
                }
            }
        }
    }
    return 0;    
}

/*
linux|linux console:\
	:am				OK
	:eo				OK
	:mi				OK
	:ms				OK
	:xn				OK
	:xo				OK
	:it#8			OK
	:AL=\E[%dL
	:DC=\E[%dP		OK
	:DL=\E[%dM
	:IC=\E[%d@		OK
	:K2=\E[G
	:al=\E[L		OK
	:bl=^G			OK
	:cd=\E[J		OK
	:ce=\E[K		OK
	:cl=\E[H\E[J	OK
	:cm=\E[%i%d;%dH	OK
	:cr=^M			OK
	:cs=\E[%i%d;%dr	OK
	:ct=\E[3g
	:dc=\E[P		OK
	:dl=\E[M
	:do=^J			OK
	:ec=\E[%dX
	:ei=\E[4l		OK
	:ho=\E[H		OK
	:ic=\E[@		OK
	:im=\E[4h		OK
	:k1=\E[[A
	:k2=\E[[B
	:k3=\E[[C
	:k4=\E[[D
	:k5=\E[[E
	:k6=\E[17~
	:k7=\E[18~
	:k8=\E[19~
	:k9=\E[20~
	:kD=\E[3~
	:kI=\E[2~
	:kN=\E[6~
	:kP=\E[5~
	:kb=\177		OK
	:kd=\E[B		OK
	:kh=\E[1~
	:kl=\E[D		OK
	:kr=\E[C		OK
	:ku=\E[A		OK
	:le=^H			OK
	:mh=\E[2m		OK
	:mr=\E[7m		OK
	:nd=\E[C		OK
	:nw=^M^J		OK
	:rc=\E8			OK
	:sc=\E7			OK
	:se=\E[27m		OK
	:sf=^J			OK
	:sr=\EM
	:st=\EH
	:ta=^I			OK
	:ue=\E[24m		OK
	:up=\E[A		OK
	:us=\E[4m		OK
	:vb=200\E[?5h\E[?5l
	:ve=\E[?25h\E[?0c	OK
	:vi=\E[?25l\E[?1c	OK
	:vs=\E[?25h\E[?8c	OK
	:tc=klone+sgr:tc=ecma+color:

klone+sgr|attribute control for ansi.sys displays
	:S2=\E[11m		?
	:S3=\E[10m		?
	:mb=\E[5m		OK
	:md=\E[1m		OK
	:me=\E[0;10m	OK
	:mk=\E[8m		OK
	:mr=\E[7m		OK
	:..sa=\E[0;10%?%p1%t;7%;%?%p2%t;4%;%?%p3%t;7%;%?%p4%t;5%;%?%p6%t;1%;%?%p7%t;8%;%?%p9%t;11%;m
	:se=\E[m		OK
	:so=\E[7m		OK
	:ue=\E[m		OK
	:us=\E[4m		OK
	:tc=klone+acs:

ecma+color|color control for ECMA-48-compatible terminals
    :Co#8			OK
    :NC#3			OK
    :pa#64			OK
    :AB=\E[4%p1%dm	OK
    :AF=\E[3%p1%dm	OK
    :op=\E[39;49m:  OK

?:
    :cv=\E[%dd		OK
    :cd=\E[0J		OK
    :ce=\E[0K		OK
*/

