/* 
 * tclWinCEconsole.c --
 *
 *	This file contains the main entry point for tclsh on Windows CE.
 *	It is adapted with minor modifications from Rainer Keuchel's
 *	celib-based w32console package, w32cosole.c file, originally
 *	written for tvision by (rk).
 *	celib Time-stamp: <12/02/02 06:41:40 keuchel@netwave.de>
 *
 * Copyright (c) 2003 ActiveState Corporation
 *
 * See the file "license.terms" for information on usage and redistribution
 * of this file, and for a DISCLAIMER OF ALL WARRANTIES.
 *
 * RCS: @(#) $Id: Exp $
 */

// The buffer is stupid! Too much memcpy()! Should use lines array.

#include <windows.h>
#include <stdio.h>
#include <signal.h>
#include <termios.h>
#include "tclWinCEconsole.h"

//////////////////////////////////////////////////////////////////////

extern HWND hMainWnd;
extern char *szTitle;
extern char *szClassName;

Window *pCurrentWnd;

//////////////////////////////////////////////////////////////////////

int console_read(int fd, unsigned char *buf, int size);
int console_write(int fd, unsigned char *buf, int size);
int console_ioctl(int fd, int flags, void *arg);

//////////////////////////////////////////////////////////////////////

int w32console_usefunctionkeys = 1;
// set to 0 by telnet
int w32console_handle_signal_chars = 1;
// set to 0 by telnet
int w32console_icrlf = 1;
// unused
int w32console_optimize_cursor = 0;

// used for terminfo csr
int scrollstart, scrollend;

int g_nCharWidth[256];

//////////////////////////////////////////////////////////////////////

// Automagically generated by FontTool
LOGFONT lf = {
    -10, // lfHeight
    0,  // lfWidth
    0, // lfEscapement
    0, // lfOrientation
    400, // lfWeight
    0, // lfItalic
    0, // lfUnderline
    0, // lfStrikeOut
    0, // lfCharSet
    3, // lfOutPrecision
    2, // lfClipPrecision
    1, // lfQuality
    49, // lfPitchAndFamily
    "Courier New" // lfFaceName
};

int wincolors[] = {
    RGB(0x00, 0x00, 0x00), // BLACK
    RGB(0xFF, 0xFF, 0xFF), // WHITE
    RGB(0x00, 0x00, 0xFF), // BLUE
    RGB(204, 0x00, 0x00), // RED
    RGB(0x00, 0xFF, 0x00), // GREEN
    RGB(204, 204, 204), // LIGHTGRAY
    RGB(153, 153, 153), // DARKGRAY
    RGB(0x00, 0xFF, 0xFF), // CYAN
    RGB(0xFF, 0x00, 0x00), // BLACK + BLINK (red)
    RGB(0xFF, 0x00, 0x00), // WHITE + BLINK (red)
    RGB(0xFF, 0x00, 0x00), // BLUE + BLINK (red)
    RGB(0xFF, 0x00, 0x00), // RED + BLINK (red)
    RGB(0xFF, 0x00, 0x00), // GREEN + BLINK (red)
    RGB(0xFF, 0x00, 0x00), // LIGHT + BLINK (red)
    RGB(0xFF, 0x00, 0x00), // DARK + BLINK (red)
    RGB(0xFF, 0x00, 0x00) // CYAN + BLINK (red)
};

int
console_start()
{
    int rows = 0;
    int cols = 0;
    TEXTMETRIC tm;
    HDC hdc;
    HFONT hFont, hOldFont;
    UINT cx, cy;
    char buf[126];

    Window *pNew = malloc(sizeof(Window));

    if (XCEGetEnvironmentVariableFromRegA("COLS", buf, sizeof(buf)) > 0) {
	cols = atoi(buf);
    }
    if (XCEGetEnvironmentVariableFromRegA("ROWS", buf, sizeof(buf)) > 0) {
	rows = atoi(buf);
    }

    if (rows == 0 || cols == 0) {
	if ((hdc = GetDC(NULL)) == NULL)
	    XCEShowMessageA("Cannot get screen DC");

	if ((hFont = console_create_font()) == NULL)
	    XCEShowMessageA("Cannot create font");

	hOldFont = SelectObject(hdc, hFont);
	GetTextMetrics(hdc, &tm);
	SelectObject(hdc, hOldFont);
	DeleteObject(hFont);
	ReleaseDC(NULL, hdc);

	cx = GetSystemMetrics(SM_CXSCREEN);
	cy = GetSystemMetrics(SM_CYSCREEN);
	cy -= GetSystemMetrics(SM_CYCAPTION);

	cols = cx / tm.tmAveCharWidth;
	rows = (cy / tm.tmHeight) - 2;
    }

    pCurrentWnd = window_create(pNew, NULL, 0, 0, cols, rows);

    scrollstart = 0;
    scrollend = rows - 1;

    // old
    //xceinitconsole(console_write, console_read);
    xceinitconsole2(hMainWnd, console_write, console_read, console_ioctl);

    return 0;
}

HFONT 
console_create_font()
{
    char *p;

    if ((p = getenv("CONSOLEFONTSIZE")) != NULL) {
	lf.lfHeight = -(atoi(p) + 3);
    }

    return CreateFontIndirect(&lf);
}

Window *
window_create(Window *this, Window *parent, int _x, int _y, int _r, int _b)
{
    DWORD dwStyle;
    int x, y, width, height;
    HDC hdc;
    HWND hwndParent = NULL;
    int i;
    int color;
    int size;
    RECT r;

    this->cx = _x;
    this->cy = _y;
    this->cwidth = _r - _x;
    this->cheight = _b - _y - 2; // magic -2 hack JH
    this->ctype = _NOCURSOR;
    this->curcx = 0;
    this->curcy = 0;
    //this->bg = DARKGRAY;
    this->bg = WHITE;
    this->fg = BLACK;

    this->linemode = 1;
    this->echo = 1;

    this->inbuf = malloc(sizeof(PFILE));
    this->inbuf->_base = malloc(512);
    this->inbuf->_cnt = 0;

    x = 0; 
    y = 0;
    width = 0; 
    height = 0;

    size = this->cwidth * (this->cheight + 1) * sizeof(short);
    this->buf = (unsigned short *) malloc(size);

    color = (this->fg << 4) | this->bg;

    for (i = 0; i < (this->cheight + 1) * this->cwidth; i++)
	this->buf[i] = (color << 8) | ' ';

    this->parent = parent;

    if (parent) {
	hwndParent = parent->hwnd;
	dwStyle = WS_VISIBLE;

	if ((this->hwnd = CreateWindow(szClassName, szTitle, dwStyle,
		x, y, width, height, hwndParent, 
		NULL, NULL, NULL)) == NULL) {
	    MessageBox(NULL, "Cannot create window", szTitle, MB_OK);
	    exit(1);
        }
    } else {
	this->hwnd = hMainWnd;
    }

    SetWindowLongW(this->hwnd, GWL_USERDATA, (long) this);

    hdc = GetDC(this->hwnd);
    this->hFont = console_create_font();
    SelectObject(hdc, this->hFont);
    GetTextMetrics(hdc, &this->tm);
    XCEGetCharWidthA(hdc, 0, 255, g_nCharWidth);
    ReleaseDC(this->hwnd, hdc);

  //this->nCharWidth = this->tm.tmMaxCharWidth;
    this->nCharWidth = this->tm.tmAveCharWidth;

    // When we set CONSOLEFONTSIZE to 9, tmAveCharWidth is 8 and
    // all in g_nCharWidth are 7...
    if (this->tm.tmAveCharWidth != g_nCharWidth[0]) {
	this->nCharWidth = g_nCharWidth[0];
    }

    this->nCharHeight = this->tm.tmHeight;

    x = _x * this->nCharWidth;
    y = _y * this->nCharHeight;
    width = (this->cwidth + 1) * (this->nCharWidth);
    height = (this->cheight + 1) * (this->nCharHeight);

    if (this->hwnd == hMainWnd) {
	r.left = x;
	r.top = y;
	r.right = x + width;
	r.bottom = y + height;

#ifdef UNDER_CE
	AdjustWindowRect(&r, WS_OVERLAPPED, FALSE);
#else
	AdjustWindowRect(&r, WS_OVERLAPPED, TRUE);
#endif

	width = r.right - r.left;
	height = r.bottom - r.top;
    } else {
	r.left = x;
	r.top = y;
	r.right = x + width;
	r.bottom = y + height;

	AdjustWindowRect(&r, WS_OVERLAPPED, FALSE);

	width = r.right - r.left;
	height = r.bottom - r.top;
    }

    GetWindowRect(this->hwnd, &r);
    // on pocket emulator, the top menubar hides the caption
    XCEMoveWindow(this->hwnd, r.left, r.top, width, height, TRUE);

    return this;
}

void
window_delete(Window *this)
{
    free(this->buf);
}

int
window_gotoxy(Window *this, int x, int y)
{
    window_update_cursor(this, 0);

    this->curcx = x - 1; 
    this->curcy = y - 1;

    if (this->curcx > this->cwidth)
	this->curcx = this->cwidth;
    if (this->curcy > this->cheight)
	this->curcy = this->cheight;

    window_update_cursor(this, 1);

    return 0;
}

int
window_getxy(Window *this, int *x, int *y)
{
    *x = this->curcx + 1; 
    *y = this->curcy + 1;

    return 0;
}

void
window_setcursortype(Window *this, int type)
{
    this->ctype = type;
}

// This is usually only called with one char...

int
window_output(Window *this, const char *s)
{
    const unsigned char *p;
    HDC hdc;
    int x, y;
    unsigned short color;

    window_update_cursor(this, 0);

    hdc = GetDC(this->hwnd);
    SelectObject(hdc, this->hFont);

    x = this->curcx * this->nCharWidth;
    y = this->curcy * this->nCharHeight;

    for (p = (unsigned char *) s; *p; p++) {
	if (p[0] == '\r') {
	    x = 0;
	    this->curcx = 0; 
	    continue;
	} else if (p[0] == '\n') {
	    x = 0;
	    this->curcx = 0; 

	    if (this->curcy + 1 >= this->cheight) {
		window_scroll(this, 1);
            } else {
		y += this->nCharHeight;
		this->curcy++;
            }

	    continue;
        }

	if (this->curcx < this->cwidth) {
	    wchar_t wc;

	    SetTextColor(hdc, wincolors[this->fg]);
	    SetBkColor(hdc, wincolors[this->bg]);

#if 1	  
	    wc = *p;

	    ExtTextOutW(hdc, x, y, 0, NULL, &wc, 1, NULL);
#else
	    ExtTextOut(hdc, x, y, 0, NULL, (char *) p, 1, NULL);
#endif
	    color = (this->fg << 4) | (this->bg);

	    this->buf[this->curcy * this->cwidth + this->curcx] 
		= (color << 8) | *p;
	}

	this->curcx++;

	x += this->nCharWidth;
    }

    ReleaseDC(this->hwnd, hdc);

    window_update_cursor(this, 1);

    return 0;
}

void
window_handle_timer(Window *this, HWND hwnd)
{
    static int flag = 0;

    if (this)
	window_update_cursor(this, flag);

    flag = !flag;
}

void
window_handle_paint(Window *this, HWND hwnd)
{
    PAINTSTRUCT ps;
    HDC hdc;
    int row;
    Window *pWnd = this;
    RECT r;

    BeginPaint(hwnd, &ps);
    hdc = ps.hdc;

    SelectObject(hdc, this->hFont);

    // fill right/bottom margins 
    GetClientRect(this->hwnd, &r);
    r.top = r.bottom - this->nCharHeight;
    FillRect(hdc, &r, (HBRUSH) (COLOR_WINDOW+1));

    GetClientRect(this->hwnd, &r);
    r.left = r.right - this->nCharWidth;
    FillRect(hdc, &r, (HBRUSH) (COLOR_WINDOW+1));

    for (row = 0; row <= pWnd->cheight; row++) {
	window_update_row(pWnd, row);
    }

    EndPaint(hwnd, &ps);

    window_update_cursor(this, 1);
}

void
window_clearscreen(Window *this)
{
    int i;
    int color = (this->fg << 4) | this->bg;
  
    for (i = 0; i < this->cheight * this->cwidth; i++)
	this->buf[i] = (color << 8) | ' ';

    InvalidateRect(this->hwnd, NULL, TRUE);
}

void
window_clear_to_end_of_screen(Window *this)
{
    int i;
    int color = (this->fg << 4) | this->bg;
    int row;

    for (i = this->curcy * this->cwidth; i < this->cheight * this->cwidth; i++)
	this->buf[i] = (color << 8) | ' ';

    for (row = this->curcy; row < this->cheight; row++)
	window_update_row(this, row);
}

void
window_cleartoeol(Window *this)
{
    int i;
    int color = (this->fg << 4) | this->bg;

    for (i = this->curcx; i < this->cwidth; i++)
	this->buf[this->curcy * this->cwidth + i] = (color << 8) | ' ';

    window_update_row(this, this->curcy);
}

void
window_insertline(Window *this)
{
    int i;
    int color = (this->fg << 4) | this->bg;
    int row;

    for (row = scrollend - 1; row >= this->curcy; row--) {
	memmove(&this->buf[(row+1) * this->cwidth],
		&this->buf[(row+0) * this->cwidth],
		this->cwidth * sizeof(short));
    }

    for (i = this->curcx; i < this->cwidth; i++)
	this->buf[this->curcy * this->cwidth + i] = (color << 8) | ' ';

    for (row = this->curcy; row <= scrollend; row++)
	window_update_row(this, row);
}

void
window_deleteline(Window *this)
{
    int i;
    int color = (this->fg << 4) | this->bg;
    int row;

    for (row = this->curcy; row < scrollend; row++) {
	memmove(&this->buf[(row+0) * this->cwidth],
		&this->buf[(row+1) * this->cwidth],
		this->cwidth * sizeof(short));
    }

    for (i = 0; i < this->cwidth; i++)
	this->buf[scrollend * this->cwidth + i] = (color << 8) | ' ';

    for (row = this->curcy; row <= scrollend; row++)
	window_update_row(this, row);
}

void
window_scroll(Window *w, int n)
{
    int i, j;
    int color = (w->fg << 4) | w->bg;
    HDC hdc;
    RECT r;
    unsigned short *ps;

    int linesize = w->cwidth * sizeof(w->buf[0]);
    int l1 = 0;
    int l2 = w->cwidth;

    // only for n = 1 now...
    n = 1;

    // move lines up, not very fast...
    for (i = 0; i < w->cheight-1; i++) {
	memcpy(&w->buf[l1], &w->buf[l2], linesize);
	l1 += w->cwidth;
	l2 += w->cwidth;
    }

    // clear last line
    ps = &w->buf[(w->cheight - 1) * w->cwidth];
    for (j = 0; j < w->cwidth; j++)
	*ps++ = (color << 8) | ' ';

    ScrollWindowEx(w->hwnd, 0, -(w->nCharHeight), 
	    NULL, NULL, NULL, NULL, 0);

    // fill new area
    GetClientRect(w->hwnd, &r);
    r.top = (w->cheight - 1) * w->nCharHeight;

    hdc = GetDC(w->hwnd);
    FillRect(hdc, &r, (HBRUSH) (COLOR_WINDOW+1));
    ReleaseDC(w->hwnd, hdc);

    window_update_row(w, w->cheight-1);
}

void
window_update_row(Window *this, int row)
{
    char *p;
    HDC hdc;
    int x, y;
    int col;
    Window *pWnd = this;
    unsigned short *pb;
    int oldcolor;
    int colorchange = 0;
    char buf[100];

    hdc = GetDC(this->hwnd);
    SelectObject(hdc, this->hFont);

    x = 0;
    y = row * this->nCharHeight;

    pb = &pWnd->buf[row * pWnd->cwidth];

    // simple optimization when whole row has same color...
    // we should output chunks with same color...
    oldcolor = (*pb) >> 8;
    for (col = 0, p = buf; col < pWnd->cwidth; col++, pb++) {
	int color = (*pb) >> 8;

	if (color != oldcolor) {
	    colorchange = 1;
	    break;
	}

	*p++ = (*pb) & 0xFF;
    }

    pb = &pWnd->buf[row * pWnd->cwidth];

    if (!colorchange) {
	int color = oldcolor;

	SetTextColor(hdc, wincolors[color >> 4]);
	SetBkColor(hdc, wincolors[color & 0x0F]);

	ExtTextOut(hdc, x, y, 0, NULL, (char *) buf, pWnd->cwidth, NULL);
    } else {
	for (col = 0; col < pWnd->cwidth; col++, pb++) {
	    unsigned char c = (*pb) & 0xFF;
	    int color = (*pb) >> 8;
      
	    SetTextColor(hdc, wincolors[color >> 4]);
	    SetBkColor(hdc, wincolors[color & 0x0F]);

	    ExtTextOut(hdc, x, y, 0, NULL, (char *) &c, 1, NULL);

	    x += this->nCharWidth;
	}
    }

    ReleaseDC(this->hwnd, hdc);

    window_update_cursor(this, 1);
}

void
window_update_cursor(Window *this, int flag)
{
    int x, y;
    HDC hdc;
    BYTE ch;
    unsigned short s;
    int color;
    int fg, bg;

    x = this->curcx * this->nCharWidth;
    y = this->curcy * this->nCharHeight;

    hdc = GetDC(this->hwnd);
    SelectObject(hdc, this->hFont);

    s = this->buf[this->curcy * this->cwidth + this->curcx];

    ch = s & 0xFF;
    color = s >> 8;

    if (flag) {
	bg = color >> 4;
	fg = color & 0xF;
    } else {
	fg = color >> 4;
	bg = color & 0xF;
    }

    SetTextColor(hdc, wincolors[fg]);
    SetBkColor(hdc, wincolors[bg]);

    //XCETrace("UpdateCursor(%d, %d, %d, '%c')", flag, x, y, ch);

    ExtTextOut(hdc, x, y, ETO_OPAQUE, NULL, &ch, 1, NULL);

    ReleaseDC(this->hwnd, hdc);
}

int
gotoxy(int x, int y)
{
    window_gotoxy(pCurrentWnd, x, y);

    return 0;
}

int
getxy(int *x, int *y)
{
    window_getxy(pCurrentWnd, x, y);

    return 0;
}

int
clrscr()
{
    window_clearscreen(pCurrentWnd);

    return 0;
}

int
clrtoeos()
{
    window_clear_to_end_of_screen(pCurrentWnd);

    return 0;
}

int
clreol()
{
    window_cleartoeol(pCurrentWnd);
    
    return 0;
}

int
insertline()
{
    window_insertline(pCurrentWnd);
    
    return 0;
}

int
deleteline()
{
    window_deleteline(pCurrentWnd);
    
    return 0;
}

int
textbackground(int color)
{
    pCurrentWnd->bg = color;
    return 0;
}

int
textcolor(int color)
{
    pCurrentWnd->fg = color;
    return 0;
}

int
setcursortype(int type)
{
    window_setcursortype(pCurrentWnd, type);

    return 0;
}

int
window_create_new(int x, int y, int w, int h)
{
    if (x == 1 && y == 1 && w == 80 && h == 25) {
	if (pCurrentWnd->parent) {
	    DestroyWindow(pCurrentWnd->hwnd);
	    pCurrentWnd = pCurrentWnd->parent;
	    /* TODO: delete called from WM_DESTROY */
        }
    } else {
	Window *pNew = malloc(sizeof(Window));

	pCurrentWnd = window_create(pNew, pCurrentWnd, x, y, w, h);
    }
    return 0;
}

/* these are not really needed as window() is implemented
   using real windows */

int
gettext(int x, int y, int r, int b, void *buf)
{
    int row, col;
    unsigned short *p = (unsigned short *) buf;

    for (row = y - 1; row < b; row++) {
	for (col = x - 1; col < r; col++)
	    *p++ = pCurrentWnd->buf[pCurrentWnd->cwidth * row + col];
    }

    return 0;
}

int
puttext(int x, int y, int r, int b, void *buf)
{
    int row, col;
    unsigned short *p = (unsigned short *) buf;

    for (row = y - 1; row < b; row++) {
	for (col = x - 1; col < r; col++)
	    pCurrentWnd->buf[pCurrentWnd->cwidth * row + col] = *p++;
    }

    InvalidateRect(pCurrentWnd->hwnd, NULL, FALSE);

    return 0;
}

int 
gettextinfo(struct text_info *ti)
{
    ti->winleft = pCurrentWnd->cx;
    ti->wintop = pCurrentWnd->cy;
    ti->winright = pCurrentWnd->cx + pCurrentWnd->cwidth;
    ti->winbottom = pCurrentWnd->cy + pCurrentWnd->cheight;
    ti->attribute = (pCurrentWnd->fg << 4) | pCurrentWnd->bg;
    ti->screenheight = 25;
    ti->screenwidth = 80;
    ti->curx = pCurrentWnd->curcx;
    ti->cury = pCurrentWnd->curcy;

    return 0;
}

int 
textattr(int attr)
{
    pCurrentWnd->fg = (attr >> 4);
    pCurrentWnd->bg = (attr & 0x0F);

    return 0;
}

static char console_inputbuf[100];
static char *console_inputbufp = console_inputbuf;

int
cgetch()
{
    MSG msg;

    window_update_cursor(pCurrentWnd, 1);

    if (*console_inputbufp) {
	return *console_inputbufp++;
    } else {
	console_inputbuf[0] = 0;
	console_inputbufp = console_inputbuf;
    }

    while (1) {
	// This gets messages only for the calling thread and
	// does not seem to work when called from another thread
	// than the window thread...

	GetMessage(&msg, NULL, 0, 0);

	if (msg.message == WM_KEYDOWN && w32console_usefunctionkeys) {
	    char *buf = console_inputbuf;

	    switch(msg.wParam) {
		case VK_SPACE:
		    if (GetKeyState(VK_CONTROL)) // Emacs: C-Space
			return 0;
		    break;
		case VK_BACK:
		    strcpy(buf, "\177");
		    break;
		case VK_RIGHT:
		    strcpy(buf, "\033[C");
		    break;
		case VK_LEFT:
		    strcpy(buf, "\033[D");
		    break;
		case VK_UP:
		    strcpy(buf, "\033[A");
		    break;
		case VK_DOWN:
		    strcpy(buf, "\033[B");
		    break;
		default:
		    buf[0] = 0;
		    break;
            }

	    if (buf[0] != 0) {
		console_inputbufp = buf;
		return *console_inputbufp++;
	    }
        }

	TranslateMessage(&msg);
	DispatchMessage(&msg);

	if (msg.message == WM_CHAR) {
	    if (msg.wParam == '\r' && w32console_icrlf)
		msg.wParam = '\n';

	    // some devices have strange mapping to non-ascii!
	    if (msg.wParam == 0x2019)
		msg.wParam = '\'';

	    // telnet escape
	    if (msg.wParam == ']' && GetKeyState(VK_CONTROL))
		msg.wParam &= 0x1F;

	    if (msg.wParam == '\003' && w32console_handle_signal_chars)
		xceraise(SIGINT);
	    else
		return msg.wParam;
        }
    }

    return 0; // never reached
}

// get char with echo

int
cgetche()
{
    int c;
    char buf[2];

    while (1) {
	c = cgetch();

	if (c == 0x08 && pCurrentWnd->linemode == 1) {
	    int x, y;

	    getxy(&x, &y);

	    if (x > 1) {
		gotoxy(x - 1, y);
		cputch(' ');
		gotoxy(x - 1, y);

		return c;
	    }
	} else {
	    break;
	}
    }
    
    if (c != 27 && c != 0) {
	buf[0] = c;
	buf[1] = 0;

	cputs(buf);
    }
    return c;
}

int
cputs(const char *s)
{
    const char *p;

    for (p = s; *p; p++)
	cputch(*p);

    return 0;
}

int
cprintf(const char *fmt, ...)
{
    va_list ap;
    char buf[1024];
    int len;

    va_start(ap, fmt);
    len = vsprintf(buf, fmt, ap);

    cputs(buf);

    return len;
}

int
cputch(int c)
{
    static char seq[10];
    static char *pseq;
    static int escape = 0;

    unsigned char buf[2];

    // This seems to be DM=242, DATAMARK. Probably OOB...
    // It is sent when hitting ^G in Emacs under Telnet.
    // we just ignore it... It's an accent character...
    if (c == 0xF2) {
	return c;
    }

    if (c == '\033') {
	pseq = seq;
	*pseq++ = c;
	escape++;
	return c;
    } else if (c == '\t') {
	int x = pCurrentWnd->curcx; 
	int y = pCurrentWnd->curcy; 

	x = x / 8;
	x = x + 1;
	x = x * 8;

	gotoxy(x+1, y+1);

	return c;
    } else if (c == '\b') {
	int x, y;
	    
	getxy(&x, &y);

	gotoxy(x - 1, y);

#if 0
	// readline uses backspace for moving left...
	cputch(' ');
	gotoxy(x - 1, y);
#endif

	return c;
    } else if (c == '\a') {
	MessageBeep(0xFFFFFFFF);
	return c;
    }

    if (escape == 1 && c != '[')
	escape = 0;

    if (escape) {
	*pseq++ = c;
	escape++;

	switch(c) {
	    case 'P': // delete char
	    {
		Window *w = pCurrentWnd;
		int i;
		int start, end;
		int color = (pCurrentWnd->fg << 4) | pCurrentWnd->bg;

		start = w->curcy * w->cwidth + w->curcx;
		end = start + w->cwidth - w->curcx;

		for (i = start; i < end; i++) {
		    w->buf[i] = w->buf[i+1];
		}

		w->buf[end] = (color << 8) | ' ';
	    
		window_update_row(w, w->curcy);

		*pseq++ = 0;
	    }
	    escape = 0;
	    break;
	    case 'A': // move up
	    {
		int x, y;
	    
		*pseq++ = 0;
	    
		getxy(&x, &y);
		gotoxy(x, y - 1);
	    }
	    escape = 0;
	    break;
	    case 'B': // move down
	    {
		int x, y;
	    
		*pseq++ = 0;
	    
		getxy(&x, &y);
		gotoxy(x, y + 1);
	    }
	    escape = 0;
	    break;
	    case 'C': // move right
	    {
		int x, y;
	    
		*pseq++ = 0;
	    
		getxy(&x, &y);
		gotoxy(x + 1, y);
	    }
	    escape = 0;
	    break;
	    case 'D': // move left
	    {
		int x, y;
	    
		*pseq++ = 0;
	    
		getxy(&x, &y);
		gotoxy(x - 1, y);
	    }
	    escape = 0;
	    break;
	    case 'K': 
	    {
		*pseq++ = 0;
		clreol();
	    }
	    escape = 0;
	    break;
	    case 'L': 
	    {
		*pseq++ = 0;
		insertline();
	    }	    
	    escape = 0;
	    break;
	    case 'M': 
	    {
		*pseq++ = 0;
		deleteline();
	    }	    
	    escape = 0;
	    break;
	    case 'm':
	    {
		int code;

		*pseq++ = 0;

		if (strcmp(seq, "\033[m") == 0) {
		    strcpy(seq, "\033[0m");
		}

		if (sscanf(seq, "\033[%dm", &code) == 1) {
		    switch(code) {
			case 0:
			    textbackground(WHITE);
			    textcolor(BLACK);
			    break;
			case 7:
			    textbackground(BLACK);
			    textcolor(WHITE);
			    break;
			default:
			    break;
		    }
		}
	    }
	    escape = 0;
	    break;
	    case 'J':
	    {
		*pseq++ = 0;

		if (strcmp(seq, "\033[J") == 0) { // cd
		    clrtoeos();
		} else if (strcmp(seq, "\033[2J") == 0) { // cl
		    clrscr();
		    gotoxy(1, 1);
		}
	    }
	    escape = 0;
	    break;
	    case 'H':
		// cursor move
	    {
		int x, y;

		*pseq++ = 0;

		if (strcmp(seq, "\033[H") == 0) { // home
		    gotoxy(1, 1);
		} else if (sscanf(seq, "\033[%d;%dH", &y, &x) == 2) { // cm
		    if (x <= 0)
			x = 1;
		    if (y <= 0)
			y = 1;

		    gotoxy(x, y);
		}
	    }
	    escape = 0;
	    break;
	    case 'r':
	    {
		int i1, i2;

		*pseq++ = 0;

		if (sscanf(seq, "\033[%d;%dH", &i1, &i2) == 2) {
		    // terminfo: csr
		    scrollstart = i1-1;
		    scrollend = i2-1;
		}
	    }
	    escape = 0;
	    break;
	    default:
		break;
	}
	return c;
    }

    buf[0] = c;
    buf[1] = 0;

    window_output(pCurrentWnd, (char *) buf);

    return c;
}

char *
cgets(char *buf)
{
    char *p;
    Window *w = pCurrentWnd;

    if ((p = buf) == NULL)
	return NULL;

    while (1) {
	if (w->echo)
	    *p = cgetche();
	else
	    *p = cgetch();

	if (*p == 0x08 && p > buf) // backspace
	    --p;
	else if (*p == '\n')
	    break;
	else
	    p++;
    }

    *++p = 0;

    return buf;
}

//////////////////////////////////////////////////////////////////////
// external functions

int 
console_read(int fd, unsigned char *buf, int size)
{
    Window *w = pCurrentWnd;
    int cc = 0;

    if (w->linemode == 0) {
	if (w->echo)
	    buf[cc] = cgetche();
	else
	    buf[cc] = cgetch();

	size--;
	cc++;

	while (size && *console_inputbufp) {
	    buf[cc++] = *console_inputbufp++;
	}

	return cc;
    } else if (w->linemode == 1) {
	for (cc = 0; cc < size;) {
	    if (w->inbuf->_cnt == 0) {
		cgets(w->inbuf->_base);
		w->inbuf->_cnt = strlen(w->inbuf->_base);
		w->inbuf->_ptr = w->inbuf->_base;
	    }

	    *buf = *w->inbuf->_ptr++;
	    cc++; w->inbuf->_cnt--;

	    if (*buf == '\n')
		break;

	    buf++; 
	}
	return cc;
    }
    return 0;
}

int 
console_write(int fd, unsigned char *buf, int size)
{
    int i;

    for (i = 0; i < size; i++)
	cputch(buf[i]);

    return size;
}

// 0,0 based
int
console_getcursorpos(int *px, int *py)
{
    // 1,1 based
    getxy(px, py);
    *px -= 1;
    *py -= 1;

    return 0;
}

// 0,0 based
int
console_gotoxy(int x, int y)
{
    // 1,1 based
    return gotoxy(x+1, y+1);
}

int
console_clearscreen()
{
    clrscr();
    gotoxy(1, 1);

    return 0;
}

int
console_setlinemode(int linemode)
{
    pCurrentWnd->linemode = linemode;

    if (linemode)
	w32console_icrlf = 1;
    else
	w32console_icrlf = 0;

    return 0;
}

int
console_setecho(int echo)
{
    pCurrentWnd->echo = echo;
    return 0;
}

int
console_readchar(int echo)
{
    if (echo)
	return cgetche();

    return cgetch();
}

int
console_getcolsrows(int *pcols, int *prows)
{
    *pcols = pCurrentWnd->cwidth;
    *prows = pCurrentWnd->cheight;
  
    return 0;
}

int
console_cleartoeol()
{
    return clreol();
}

int
console_settitle(const char *title)
{
    SetWindowText(hMainWnd, title);
    return 0;
}

//////////////////////////////////////////////////////////////////////
static struct termios cur_termios;

int
console_setattr(int fd, int flags, struct termios *tio)
{
    memcpy(&cur_termios, tio, sizeof(struct termios));

    if (tio->c_lflag & ICANON) {
	console_setlinemode(1);
    } else {
	console_setlinemode(0);
    }

    if (tio->c_lflag & ECHO) {
	console_setecho(1);
    } else {
	console_setecho(0);
    }

    return 0;
}

int
console_getattr(int fd, int flags, struct termios *tio)
{
    memcpy(tio, &cur_termios, sizeof(struct termios));
    return 0;
}

int
console_ioctl(int fd, int flags, void *arg)
{
    if (flags == TCSETA) {
	return console_setattr(fd, flags, arg);
    }
    else if (flags == TCGETA) {
	return console_getattr(fd, flags, arg);
    }
    else if (flags == TIOCGWINSZ) {
	struct winsize *ws = (struct winsize *) arg;
	char *p;
	int rows = 25;
	int cols = 80;

	if ((p = getenv("ROWS")) != NULL)
	    rows = atoi(p);

	if ((p = getenv("COLS")) != NULL)
	    cols = atoi(p);

	ws->ws_row = rows;
	ws->ws_col = cols;

	return 0;
    }

    errno = EINVAL;
    return -1;
}
