/* $Id: curses.c,v 1.50 2015/09/29 13:17:42 onoe Exp $ */

/*-
 * Copyright (c) 1998-2001 Atsushi Onoe
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. The name of the author may not be used to endorse or promote products
 *    derived from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
 * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
 * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
 * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/time.h>
#include <termios.h>
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <string.h>
#include <signal.h>
#include <unistd.h>

#include "curses.h"

int LINES, COLS;
WINDOW *stdscr, *curscr;
int curx, cury;
int eucjp = 0;

static struct termios save, cur;
static int delay = -1;	/* msec */
static int curattr;
static int curs_gotwinch;
static int curs_busy;

static int ifd, ofd;
static fd_set rfds;

static u_long *mem, *scr;
static u_char outbuf[BUFSIZ];	/*XXX*/
static u_char *op = outbuf;
static u_char inbuf[BUFSIZ];
static u_char *ip = inbuf;
static int isclearok, isscrollok, isendwin, isidlok, iswrapok;

#define	POS(m, y, x)	((m) + COLS * (y) + (x))
#define	MODSET(y)	(mem[COLS * LINES + (y)] |=  0x01)
#define	MODCLR(y)	(mem[COLS * LINES + (y)] &= ~0x01)
#define	ISMOD(y)	(mem[COLS * LINES + (y)] &   0x01)
#define	CONTSET(y)	(mem[COLS * LINES + (y)] |=  0x02)
#define	CONTCLR(y)	(mem[COLS * LINES + (y)] &= ~0x02)
#define	ISCONT(y)	(mem[COLS * LINES + (y)] &   0x02)

#define	_oflush()	do { \
		if (op > outbuf) do_write(ofd, outbuf, op - outbuf); \
		op = outbuf; \
	} while (0)
#define	_ock(_n)	do { \
		if (op + (_n) >= outbuf + sizeof(outbuf)) _oflush(); \
	} while (0)
#define	_num(_n)	do { \
		if ((_n) >= 100) *op++ = '0'+(_n)/100; \
		if ((_n) >= 10) *op++ = '0'+((_n)/10%10); \
		*op++ = '0'+(_n)%10; \
	} while (0);

#define	_move(_y, _x)	do { _ock(10); \
		*op++ = '\033'; *op++ = '['; _num((_y)+1); \
		*op++ = ';'; _num((_x)+1); *op++ = 'H'; \
	} while (0);
#define	_clrtoeol()	do { _ock(3); \
		*op++ = '\033'; *op++ = '['; *op++ = 'K'; \
	} while (0)
#define	_clrtobot()	do { _ock(3); \
		*op++ = '\033'; *op++ = '['; *op++ = 'J'; \
	} while (0)
#define	_clear()	do { _ock(6); \
		*op++ = '\033'; *op++ = '['; *op++ = 'H'; \
		*op++ = '\033'; *op++ = '['; *op++ = 'J'; \
	} while (0)
#define	_a_reset()	do { _ock(3); \
		*op++ = '\033'; *op++ = '['; *op++ = 'm'; \
	} while (0)
#define	_a_reverse()	do { _ock(4); \
		*op++ = '\033'; *op++ = '['; *op++ = '7'; *op++ = 'm'; \
	} while (0)
#define	_a_bold()	do { _ock(4); \
		*op++ = '\033'; *op++ = '['; *op++ = '1'; *op++ = 'm'; \
	} while (0)
#define	_a_underline()	do { _ock(4); \
		*op++ = '\033'; *op++ = '['; *op++ = '4'; *op++ = 'm'; \
	} while (0)
#define	_a_color(_col)	do { _ock(5); \
		*op++ = '\033'; *op++ = '['; *op++ = '3'; \
		*op++ = '0' + (_col); *op++ = 'm'; \
	} while (0)
#define	_attrset(_attr)	do { \
		_a_reset(); \
		if (_attr & A_COLOR) _a_color(_attr >> 12); \
		if (_attr & (A_REVERSE|A_STANDOUT)) _a_reverse(); \
		if (_attr & A_BOLD) _a_bold(); \
		if (_attr & A_UNDERLINE) _a_underline(); \
	} while (0)
#define	_addch(_ch)	do { _ock(1); \
		if (eucjp) { \
		  int _cs = _ch & A_CHARSET; \
		  if (_cs == A_ASCII) { \
		    *op++ = (_ch & 0x7f); \
		  } else { \
		    *op++ = '='; \
		  } \
		} else { \
		  *op++ = (_ch & 0x7f); \
		} \
	} while (0)
#define	_addch2(_c1, _c2)	do { _ock(2); \
		if (eucjp) { \
		  int _cs = _c1 & A_CHARSET; \
		  if (_cs == A_JIS || _cs == A_OLDJIS) { \
		    *op++ = (_c1 | 0x80); *op++ = (_c2 | 0x80); \
		  } else { \
		    *op++ = 0xa2; *op++ = 0xa2; \
		  } \
		} else { \
		  *op++ = (_c1 & 0x7f); *op++ = (_c2 & 0x7f); \
		} \
	} while (0)
#define	_addc96(_ch)	do { if (!eucjp) { _ock(3); \
		*op++ = '\033'; *op++ = 'N'; *op++ = (_ch & 0x7f); \
		} \
	} while (0)
#define	_charset(_cs)	do { if (!eucjp) { _ock(4); \
		*op++ = '\033'; \
		if (_cs & A_DWIDTH) *op++ = '$'; \
		if (_cs & A_CS96SET) *op++ = '.'; \
		else if (_cs != A_JIS && _cs != A_OLDJIS) *op++ = '('; \
		*op++ = a_csunpack(_cs); \
	} } while (0)
#define	_insertln(_n)	do { _ock(6); \
		*op++ = '\033'; *op++ = '['; _num(_n); *op++ = 'L'; \
	} while (0);
#define	_deleteln(_n)	do { _ock(6); \
		*op++ = '\033'; *op++ = '['; _num(_n); *op++ = 'M'; \
	} while (0);
#ifdef notdef
#define	_scrlup(_n)	do { int _i; _ock(2 * _n); \
		for (_i = 0; _i < _n; _i++) { *op++ = '\033'; *op++ = 'D'; } \
	} while (0)
#define	_scrldown(_n)	do { int _i; _ock(2 * _n); \
		for (_i = 0; _i < _n; _i++) { *op++ = '\033'; *op++ = 'M'; } \
	} while (0)
#endif
#define	_beep()		do { write(ofd, "\007", 1); } while (0)

#define	CURS_HOLD()	(curs_busy++)
#define	CURS_RELE()	do {				\
	if (curs_busy == 1 && curs_gotwinch) {		\
		curs_gotwinch = 0;			\
		curs_dowinch();				\
	}						\
	curs_busy--;					\
} while (0)

static void
do_write(int fd, u_char *buf, int len)
{
	int cc, off;

	for (off = 0; off < len; off += cc) {
		cc = write(fd, buf + off, len - off);
		if (cc < 0)
			break;
	}
}

static void
curs_cleanup(int sig)
{
	sigset_t mask;

	endwin();
	signal(sig, SIG_DFL);
	sigemptyset(&mask);
	sigaddset(&mask, sig);
	sigprocmask(SIG_UNBLOCK, &mask, NULL);
	kill(getpid(), sig);
	if (sig != SIGTSTP)
		exit(0);
}

static void
curs_susp(int sig)
{
	curs_cleanup(sig);

	/* resume */
	signal(sig, curs_susp);
	refresh();
}

static void
curs_dowinch(void)
{
	struct winsize ws;
	u_long *newmem;
	unsigned int x, y;
	int lines, cols;
	u_long *s, *d;

	if (ioctl(ofd, TIOCGWINSZ, &ws) < 0)
		ws.ws_row = ws.ws_col = 0;

	/* XXX */
	if (ws.ws_row == 0)
		ws.ws_row = 24;
	if (ws.ws_col == 0)
		ws.ws_col = 80;

	/* copy memory image (include modify flag area) */
	if ((newmem = malloc((ws.ws_row + 1) * ws.ws_col * sizeof(u_long))) == NULL)
		curs_cleanup(SIGQUIT);
	if (mem)  {
		lines = (unsigned)LINES < ws.ws_row ? LINES :  ws.ws_row;
		cols =  (unsigned)COLS < ws.ws_col  ? COLS : ws.ws_col;
	} else {
		lines = cols = 0;
	}
	for (y = 0; y < (unsigned int)lines; y++) {
		s = POS(mem, y, 0);
		d = &newmem[ws.ws_col * y];
		for (x  = 0; x < (unsigned int)cols; x++)
			*d++ = *s++;
		for (; x < ws.ws_col; x++)
			*d++ = 0;
	}
	d = &newmem[ws.ws_col * y];
	for (; y < ws.ws_row; y++) {
		for (x = 0; x < ws.ws_col; x++)
			*d++ = 0;
	}
	if (mem)
		free(mem);
	mem = newmem;

	/* clear screen image */
	if (scr)
		free(scr);
	if ((scr = malloc(ws.ws_row * ws.ws_col * sizeof(u_long))) == NULL)
		curs_cleanup(SIGQUIT);
	_clear();
	d = scr;
	for (y = 0; y < ws.ws_row; y++)
		for (x = 0; x < ws.ws_col; x++)
			*d++ = 0;

	LINES = ws.ws_row;
	COLS = ws.ws_col;
	for (y = 0; y < LINES; y++)
		MODSET(y);
}

/*ARGSUSED*/
static void
curs_winch(int sig)
{

	if (curs_busy) {
		curs_gotwinch = 1;
		return;
	}
	curs_dowinch();
}

WINDOW *
initscr(void)
{

	ofd = fileno(stdout);
	ifd = fileno(stdin);
	FD_ZERO(&rfds);
	FD_SET(ifd, &rfds);

	if (tcgetattr(ifd, &save) < 0)
		return NULL;
	cur = save;

	curs_winch(0);

	signal(SIGTERM, curs_cleanup);
	signal(SIGINT, curs_cleanup);
	signal(SIGQUIT, curs_cleanup);
	signal(SIGWINCH, curs_winch);
	signal(SIGTSTP, curs_susp);
	return stdscr;
}

int
cangetch(int msec)
{
	fd_set fds;
	struct timeval tvbuf, *tout;

	if (ip > inbuf)
		return 1;
	if (msec < 0)
		tout = NULL;
	else {
		tout = &tvbuf;
		tout->tv_sec = msec / 1000;
		tout->tv_usec = (msec % 1000) * 1000;
	}

	fds = rfds;
	return select(ifd + 1, &fds, NULL, NULL, tout) > 0 ? TRUE : FALSE;
}

int
getch(void)
{
	char c;

	if (ip > inbuf)
		return *--ip;

	if (!cangetch(delay))
		return ERR;

	if (read(ifd, &c, sizeof(c)) < 0)
		return ERR;

	return (u_char)c;
}

int
ungetch(int ch)
{
	if (ip >= inbuf + sizeof(inbuf))
		return ERR;
	*ip++ = ch;
	return TRUE;
}

int
endwin(void)
{
	isendwin = 1;
	_attrset(0);
	_move(LINES-1, 0);
	_oflush();
	tcsetattr(ifd, TCSANOW, &save);
	return OK;
}

int
clrtoeol(void)
{
	int x;
	u_long *d;

	CURS_HOLD();
	d = POS(mem, cury, curx);
	for (x = curx; x < COLS; x++)
		*d++ = 0;
	MODSET(cury);
	if (cury < LINES - 1)
		CONTCLR(cury + 1);
	CURS_RELE();
	return OK;
}

int
clrtobot(void)
{
	int x, y;
	u_long *m;
#ifdef notdef	/* why not work??? */
	u_long *s;

	CURS_HOLD();
	_clrtobot();
	m = POS(mem, cury, curx);
	s = POS(scr, cury, curx);
	for (x = curx; x < COLS; x++)
		*s++ = *m++ = 0;
	for (y = cury + 1; y < LINES; y++) {
		for (x = 0; x < COLS; x++)
			*s++ = *m++ = 0;
	}
#else
	CURS_HOLD();
	m = POS(mem, cury, curx);
	for (x = curx; x < COLS; x++)
		*m++ = 0;
	MODSET(cury);
	for (y = cury + 1; y < LINES; y++) {
		for (x = 0; x < COLS; x++)
			*m++ = 0;
		MODSET(y);
		CONTCLR(y);
	}
#endif
	CURS_RELE();
	return OK;
}

int
addch(int c)
{
	u_long *m;
	int tab;
	u_long attr;

	if (curx >= COLS)
		return ERR;

	CURS_HOLD();
	m = POS(mem, cury, curx);
	if (c == '\n') {
		curx = 0;
		if (cury + 1 < LINES)
			cury++;
	} else if (c == '\b') {
		if (curx > 0)
			curx--;
	} else if (c == '\t') {
		tab = (curx + 8) / 8 * 8 - curx;
		curx += tab;
		while (tab-- > 0)
			*m++ = curattr | A_ASCII | ' ';
		MODSET(cury);
	} else {
		attr = curattr;
		if ((c >> 8) == CS_SS2)			/* JIS X0201 */
			attr |= a_cspack('I');
		else if ((c >> 16) == CS_SS3)		/* JIS X0212 */
			attr |= A_DWIDTH | a_cspack('D');
		else if ((c >> 16) == CS_SSX)
			attr |= (c << 8) & 0x00ff0000;
		else if ((c >> 24) == CS_SSX)
			attr |= c & 0x00ff0000;
		else if (c & 0x80)
			attr |= A_DWIDTH | a_cspack('B');
		else
			attr |= A_ASCII;

		if (attr & A_DWIDTH) {
			if (curx + 1 >= COLS)
				return ERR;
			*m++ = attr | ((c >> 8) & 0xff);
			*m++ = attr | (c & 0xff);
			curx += 2;
		} else {
			*m++ = attr | (c & 0xff);
			curx++;
		}
		MODSET(cury);
	}
	CURS_RELE();
	return OK;
}

/* n bytes */
int
addnstr(const char *str, int n)
{
	u_long *m;
	u_long cs;
	int tab;

	CURS_HOLD();
	m = POS(mem, cury, curx);
	while (n-- != 0 && *str != '\0') {
		if (curx >= COLS)
			break;
		if (*str == '\n') {
			curx = 0;
			if (cury + 1 < LINES)
				cury++;
			m = POS(mem, cury, curx);
			str++;
		} else if (*str == '\b') {
			if (curx > 0)
				curx--;
			str++;
		} else if (*str == '\t') {
			tab = (curx + 8) / 8 * 8 - curx;
			curx += tab;
			while (tab-- > 0)
				*m++ = curattr | A_ASCII | ' ';
			MODSET(cury);
			str++;
		} else if (*(u_char *)str == CS_SS2) {
			if (n == 0)
				break;
			str++;
			*m++ = a_cspack('I') | curattr | (u_char)*str++;
			MODSET(cury);
			curx++;
			n--;
		} else if (*(u_char *)str == CS_SS3) {
			if (curx + 1 == COLS || n == 0)
				break;
			str++;
			*m++ = A_DWIDTH | a_cspack('D') | curattr | (u_char)*str++;
			*m++ = A_DWIDTH | a_cspack('D') | curattr | (u_char)*str++;
			MODSET(cury);
			curx += 2;
			n -= 2;
		} else if (*(u_char *)str == CS_SSX) {
			if (n == 0 || ((str[1] & A_DWIDTH) && curx + 1 == COLS))
				break;
			str++;
			cs = *str++ << 16;
			if (cs & A_DWIDTH) {
				*m++ = cs | curattr | (u_char)*str++;
				*m++ = cs | curattr | (u_char)*str++;
				n -= 3;
				curx += 2;
			} else {
				*m++ = cs | curattr | (u_char)*str++;
				n -= 2;
				curx++;
			}
			MODSET(cury);
		} else if (*str & 0x80) {
			if (curx + 1 == COLS || n == 0)
				break;
			*m++ = A_DWIDTH | a_cspack('B') | curattr | (u_char)*str++;
			*m++ = A_DWIDTH | a_cspack('B') | curattr | (u_char)*str++;
			n--;
			MODSET(cury);
			curx += 2;
		} else {
			*m++ = A_ASCII | curattr | (u_char)*str++;
			MODSET(cury);
			curx++;
		}
	}
	CURS_RELE();
	return OK;
}

int
contline(int y, bool bf)
{

	if (y < 0 || y >= LINES)
		return ERR;
	if (y == 0 && bf)
		return ERR;
	CURS_HOLD();
	if (bf)
		CONTSET(y);
	else
		CONTCLR(y);
	CURS_RELE();
	return OK;
}

int
vwprintw(WINDOW *win, const char *fmt, va_list ap)
{
	char line[BUFSIZ];	/*XXX*/
	int n;

	n = vsnprintf(line, sizeof(line), fmt, ap);
	addnstr(line, -1);
	return n;
}

int
printw(const char *fmt, ...)
{
	va_list ap;
	int n;

	va_start(ap, fmt);
	n = vwprintw(stdscr, fmt, ap);
	va_end(ap);
	return n;
}

int
hline(int ch, int n)
{
	int i;
	u_long *m;

	CURS_HOLD();
	m = POS(mem, cury, curx);
	if (curx + n > COLS)
		n = COLS - curx;
	for (i = 0; i < n; i++)
		*m++ = (u_long)ch | A_ASCII;
	MODSET(cury);
	if (cury < LINES - 1)
		CONTCLR(cury + 1);
	CURS_RELE();
	return OK;
}

int
inch(void)
{
	u_long ch;

	CURS_HOLD();
	ch = *POS(mem, cury, curx);
	CURS_RELE();
	return ch;
}

int
innstr(char *str, int n)
{
	u_long *m;
	int x;

	CURS_HOLD();
	m = POS(mem, cury, curx);
	for (x = curx; n != 0; n--) {
		if (x++ == COLS) {
			*str = '\0';
			break;
		}
		*str++ = *m++ & A_CHARTEXT;
	}
	CURS_RELE();
	return OK;
}

/*ARGSUSED*/
int
scrollok(WINDOW *win, int ok)
{
	isscrollok = ok;
	return OK;
}

/*ARGSUSED*/
int
idlok(WINDOW *win, int ok)
{
	isidlok = ok;
	return OK;
}

/*ARGSUSED*/
int
clearok(WINDOW *win, int ok)
{
	isclearok = ok;
	return OK;
}

/*ARGSUSED*/
int
wrapok(WINDOW *win, int ok)
{
	iswrapok = ok;
	return OK;
}

int
cbreak(void)
{
	cur.c_lflag |= ISIG;
	cur.c_lflag &= ~ICANON;
	cur.c_cc[VMIN] = 1;
	cur.c_cc[VTIME] = 0;
	tcsetattr(ifd, TCSANOW, &cur);
	return OK;
}

int
raw(void)
{
	cur.c_lflag &= ~(ISIG|ICANON|IEXTEN);
	cur.c_iflag &= ~(IXON|IXOFF);
	cur.c_cc[VMIN] = 1;
	cur.c_cc[VTIME] = 0;
	tcsetattr(ifd, TCSANOW, &cur);
	return OK;
}

int
nonl(void)
{
	cur.c_iflag &= ~ICRNL;
	cur.c_oflag &= ~ONLCR;
	tcsetattr(ifd, TCSANOW, &cur);
	return OK;
}

int
noecho(void)
{
	cur.c_lflag &= ~ECHO;
	tcsetattr(ifd, TCSANOW, &cur);
	return OK;
}

int
echo(void)
{
	cur.c_lflag |= ECHO;
	tcsetattr(ifd, TCSANOW, &cur);
	return OK;
}

/*ARGSUSED*/
int
nodelay(WINDOW *win, int ok)
{
	delay = ok ? 0 : -1;
	return OK;
}

int
halfdelay(int tenth)
{
	delay = tenth * 100;
	return OK;
}

int
erase(void)
{
	int x, y;
	u_long *m;

	CURS_HOLD();
	m = mem;
	for (y = 0; y < LINES; y++) {
		for (x = 0; x < COLS; x++)
			*m++ = 0;
		MODSET(y);
		CONTCLR(y);
	}
	CURS_RELE();
	return OK;
}

int
attrset(int attr)
{
	curattr = attr;
	return OK;
}

int
attron(int attr)
{
	curattr |= attr;
	return OK;
}

int
attroff(int attr)
{
	curattr &= ~attr;
	return OK;
}

int
beep(void)
{
	_beep();
	return OK;
}

int
scrl(int n)
{
	u_long *m, *s;
	int x, y;
	int clrline, cpylen;
	int clroff, cpysrc, cpydst;

	if (n == 0)
		return OK;
	if (!isscrollok)
		return ERR;
	CURS_HOLD();
	if (n > 0) {
		if (n > LINES)
			n = LINES;
		cpydst = 0;
		cpysrc = COLS * n;
		cpylen = COLS * (LINES - n);
		clroff = LINES - n;
		clrline = n;
	} else {
		if (n < -LINES)
			n = -LINES;
		cpydst = COLS * -n;
		cpysrc = 0;
		cpylen = COLS * (LINES - -n);
		clroff = 0;
		clrline = -n;
	}
	if (cpylen)
		memmove(mem + cpydst, mem + cpysrc, cpylen * sizeof(u_long));
	m = POS(mem, clroff, 0);
	for (y = 0; y < clrline; y++) {
		for (x = 0; x < COLS; x++)
			*m++ = 0;
		CONTCLR(clroff + y);
	}
	if (isidlok) {
		_move(0, 0);
		if (n < 0) {
			_insertln(-n);
			for (y = 0; y < n; y++) {
				_move(y, 0);
				_clrtoeol();
			}
		} else {
			_deleteln(n);
			_move(clroff, 0);
			_clrtobot();
		}
		if (cpylen)
			memmove(scr + cpydst, scr + cpysrc, cpylen * sizeof(u_long));
		s = POS(scr, clroff, 0);
		for (y = 0; y < clrline; y++)
			for (x = 0; x < COLS; x++)
				*s++ = 0;
	} else {
		for (y = 0; y < LINES; y++)
			MODSET(y);
	}
	CURS_RELE();
	return OK;
}

int
insdelln(int n)
{
	u_long *m, *s;
	int x, y;
	int clrlen, cpylen;
	int clroff, cpysrc, cpydst;

	if (n >= LINES - cury || n <= -(LINES - cury)) {
		move(cury, 0);
		clrtobot();
		move(cury, curx);
		return OK;
	}
	if (n == 0)
		return OK;
	CURS_HOLD();
	if (n > 0) {	/* insert */
		cpysrc = cury;
		cpydst = cury + n;
		cpylen = LINES - cpydst;
		clroff = cury;
		clrlen = n;
	} else {	/* delete */
		cpydst = cury;
		cpysrc = cury + (-n);
		cpylen = LINES - cpysrc;
		clroff = LINES - (-n);
		clrlen = (-n);
	}
	if (cpylen)
		memmove(POS(mem, cpydst, 0), POS(mem, cpysrc, 0),
			(cpylen * COLS) * sizeof(u_long));
	m = POS(mem, clroff, 0);
	for (y = 0; y < clrlen; y++)
		for (x = 0; x < COLS; x++)
			*m++ = 0;
	if (isidlok) {
		_move(cury, 0);
		if (n > 0) {
			_insertln(n);
		} else {
			_deleteln(-n);
		}
		if (cpylen)
			memmove(POS(scr, cpydst, 0), POS(scr, cpysrc, 0),
				(cpylen * COLS) * sizeof(u_long));
		s = POS(scr, clroff, 0);
		for (y = 0; y < clrlen; y++)
			for (x = 0; x < COLS; x++)
				*s++ = 0;
	} else {
		for (y = 0; y < LINES; y++)
			MODSET(y);
	}
	CURS_RELE();
	return OK;
}

int
clear(void)
{
	move(0, 0);
	return clrtobot();
}

static int
wrap_line(int y)
{
	int x;
	int attr, charset, ncs;
	u_long ch1, ch2;

	attr = 0;
	charset = A_ASCII;
	x = COLS - 1;
	ch2 = 0;	/* make gcc happy */

	ch1 = *POS(mem, y, x);
	if (ch1 == 0) {
		x--;
		ch1 = *POS(scr, y, x);
		if (ch1 == 0)
			return ERR;
	}
	if (ch1 & A_DWIDTH) {
		ch2 = ch1;
		x--;
		ch1 = *POS(scr, y, x);
	}
	_move(y, x);
	if ((ch1 & (A_ATTRIBUTES|A_COLOR)) != attr) {
		attr = ch1 & (A_ATTRIBUTES|A_COLOR);
		_attrset(attr);
	}
	ncs = ch1 & A_CHARSET;
	if (ncs == A_ASCII) {
		_addch(ch1);
	} else {
		if (ncs != charset) {
			charset = ncs;
			_charset(charset);
		}
		if (ncs & A_DWIDTH) {
			_addch2(ch1, ch2);
		} else if (ncs & A_CS96SET) {
			_addc96(ch1);
		} else {
			_addch(ch1);
		}
	}

	y++;
	ch1 = *POS(scr, y, 0);
	if (ch1 & A_DWIDTH)
		ch2 = *POS(scr, y, 1);
	if ((ch1 & (A_ATTRIBUTES|A_COLOR)) != attr) {
		attr = ch1 & (A_ATTRIBUTES|A_COLOR);
		_attrset(attr);
	}
	ncs = ch1 & A_CHARSET;
	if (ncs == A_ASCII) {
		if (!a_isascii(charset)) {
			if (!eucjp)
				_charset(a_cspack('B'));
			charset = A_ASCII;
		}
		_addch(ch1);
	} else {
		if (ncs != charset) {
			charset = ncs;
			if (!eucjp)
				_charset(charset);
		}
		if (ncs & A_DWIDTH) {
			_addch2(ch1, ch2);
		} else if (ncs & A_CS96SET) {
			_addc96(ch1);
		} else {
			_addch(ch1);
		}
	}

	if (!a_isascii(charset))
		_charset(a_cspack('B'));
	if (attr)
		_attrset(0);
	return OK;
}

static int
refresh_line(int y)
{
	u_long *m, *s;
	int x;
	int ds, de, cs, ce;
	int attr, charset, ncs;

	m = POS(mem, y, 0);
	s = POS(scr, y, 0);
	ds = cs = -1;
	de = ce = 0;
	attr = 0;
	charset = A_ASCII;
	for (x = 0; x < COLS; ) {
		if (m[x] & A_DWIDTH) {
			if (m[x] != s[x] || m[x+1] != s[x+1]) {
				if (ds < 0)
					ds = x;
				de = x + 2;
			}
			if (cs < 0)
				cs = x;
			x += 2;
			ce = x;
		} else {
			if (m[x] != s[x]) {
				if (ds < 0)
					ds = x;
				de = x + 1;
			}
			if (m[x] != 0 && m[x] != (A_ASCII | ' ')) {
				if (cs < 0)
					cs = x;
				ce = x + 1;
			}
			x++;
		}
	}
	if (ds >= 0) {
		if (ce <= ds) {
			_move(y, ce);
			x = ce;
		} else {
			/*
			 * special optimization
			 *  s:  abcdefghijklmnopqr
			 *  m:           jklm
			 *      ds       cs  ce  de
			 *  m:           jklmnopqrstu
			 *      ds       cs      de ce
			 */
			if (cs > ds && de > cs && (cs - ds) * 2 > de - ds) {
				_move(y, ds);
				_clrtoeol();
				for (x = ds; x < COLS; x++)
					s[x++] = 0;
				ds = cs;
				de = ce;
			}
			_move(y, ds);
			for (x = ds; x < de; x++) {
				if (x == ce)
					break;
				if ((m[x] & (A_ATTRIBUTES|A_COLOR)) != attr) {
					attr = m[x] & (A_ATTRIBUTES|A_COLOR);
					_attrset(attr);
				}
				ncs = m[x] & A_CHARSET;
				if (ncs == A_ASCII) {
					if (!a_isascii(charset)) {
						_charset(a_cspack('B'));
						charset = A_ASCII;
					}
				} else {
					if (ncs != charset) {
						charset = ncs;
						_charset(charset);
					}
				}
				s[x] = m[x];
				if (ncs & A_CS96SET) {
					_addc96(m[x]);
				} else if (ncs & A_DWIDTH) {
					s[x + 1] = m[x + 1];
					_addch2(m[x], m[x + 1]);
					x++;
				} else {
					_addch(m[x]);
				}
			}
			if (!a_isascii(charset))
				_charset(a_cspack('B'));
			charset = A_ASCII;
		}
		if (x == ce && x < de) {
			_clrtoeol();
			while (x < de)
				s[x++] = 0;
		}
		if (attr)
			_attrset(0);
		if (iswrapok && y > 0 && ISCONT(y))
			wrap_line(y - 1);
	}
	MODCLR(y);
	return OK;
}

int
refresh(void)
{
	u_long *s;
	int y, x;

	CURS_HOLD();
	if (isclearok || isendwin) {
		if (isendwin) {
			tcsetattr(ifd, TCSANOW, &cur);
			isendwin = 0;
		}
		if (isclearok)
			isclearok = FALSE;	/* reset */
		_clear();
		s = scr;
		for (y = 0; y < LINES; y++) {
			for (x = 0; x < COLS; x++)
				*s++ = 0;
			MODSET(y);
		}
	}
	for (y = 0; y < LINES; y++) {
		if (ISMOD(y)) {
			refresh_line(y);
#ifdef CUE
			if (cangetch(0))
				break;
#endif
		}
	}
	_move(cury, curx);
	_oflush();
	CURS_RELE();
	return OK;
}

/* XXX win is not supported */
#undef wmove
int
wmove(WINDOW *win, int y, int x)
{
	return move(y, x);
}

#undef waddch
int
waddch(WINDOW *win, int ch)
{
	return addch(ch);
}

#undef waddstr
int
waddstr(WINDOW *win, const char *str)
{
	return addnstr(str, -1);
}

#undef waddnstr
int
waddnstr(WINDOW *win, const char *str, int n)
{
	return addnstr(str, n);
}

#undef winch
int
winch(WINDOW *win)
{
	return inch();
}

#undef winstr
int
winstr(WINDOW *win, char *str)
{
	return innstr(str, -1);
}

#undef winnstr
int
winnstr(WINDOW *win, char *str, int n)
{
	return innstr(str, n);
}

#undef wrefresh
int
wrefresh(WINDOW *win)
{
	return refresh();
}

#undef wclrtoeol
int
wclrtoeol(WINDOW *win)
{
	return clrtoeol();
}

#undef wclear
int
wclear(WINDOW *win)
{
	return clear();
}

#undef wstandout
int
wstandout(WINDOW *win)
{
	return standout();
}

#undef wstandend
int
wstandend(WINDOW *win)
{
	return standend();
}

#undef wattrset
int
wattrset(WINDOW *win, int attr)
{
	return attrset(attr);
}

#undef wattron
int
wattron(WINDOW *win, int attr)
{
	return attron(attr);
}

#undef wattroff
int
wattroff(WINDOW *win, int attr)
{
	return attroff(attr);
}

#if 1
/* XXX: not supported! */
WINDOW *
newwin(int nlines, int ncols, int begin_y, int begin_x)
{
	return NULL;
}

int
delwin(WINDOW *win)
{
	return ERR;
}

int
box(WINDOW *win, int verch, int horch)
{
	return ERR;
}
#endif
