/**************************************************************************
 * This program is Copyright (C) 1986-2002 by Jonathan Payne.  JOVE is    *
 * provided by Jonathan and Jovehacks without charge and without          *
 * warranty.  You may copy, modify, and/or distribute JOVE, provided that *
 * this notice is included in all the source files and documentation.     *
 **************************************************************************/

/* Win32 support routines for Jove Keyboard and Screen */

#include "jove.h"

#ifdef WIN32	/* the body is the rest of this file */

#include "fp.h"	/* scr_putchar */
#include "chars.h"
#include "screen.h"
#include "disp.h"	/* for redisplay() */

#undef CR /* sigh, used as field name in some windows header */
#include <windows.h>

INPUT_RECORD in_event[NCHARS], *eventp = in_event;	/* Input events e.g. keyboard, mouse-click */
DWORD nevents;
private HANDLE conin, conout, conerr;	/* Console handles */
private COORD curpos;
private COORD maxpos;
private HANDLE old_stdout, old_stderr;
private WORD  old_attributes;

private BOOL WINAPI ctrlHandler proto((DWORD type));	/* Control handler */
#define CHECK(fn)	{ if (!(fn)) ConsoleFail(#fn); }
private void ConsoleFail(char *fdef);

void
getTERM()
{
}

void
ttysetattr(n)
bool	n;	/* also used as subscript! */
{
	CONSOLE_SCREEN_BUFFER_INFO info;
	COORD bufsize;

	if (n) {
		if (conout == NULL) {
			/* Create our own console buffer so we can easily restore
			 * the startup environment. This also allows us to resize
			 * it to eliminate the scroll bars, at least where the
			 * full Console API is implemented (i.e. on Windows NT).
			 * (Windows 95 seems to ignore whatever we do to the window).
			 */
			old_stdout = GetStdHandle(STD_OUTPUT_HANDLE);
			old_stderr = GetStdHandle(STD_ERROR_HANDLE);
			CHECK(GetConsoleScreenBufferInfo(old_stdout, &info));
			old_attributes = info.wAttributes;
			conout = CreateConsoleScreenBuffer(GENERIC_READ|GENERIC_WRITE,
				FILE_SHARE_READ|FILE_SHARE_WRITE,
				NULL, CONSOLE_TEXTMODE_BUFFER, NULL);
			CHECK(DuplicateHandle(GetCurrentProcess(), conout,
					GetCurrentProcess(), &conerr,
					0, TRUE, DUPLICATE_SAME_ACCESS));
			bufsize.X = info.srWindow.Right-info.srWindow.Left+1;
			bufsize.Y = info.srWindow.Bottom-info.srWindow.Top+1;
			CHECK(SetConsoleScreenBufferSize(conout, bufsize));
#ifdef DEBUG
			{
				char tracebuf[100];
				DWORD version = GetVersion();
				wsprintf(tracebuf, "%s Windows %d.%d Build %d\n",
					(version > 0 ? "MS-DOS" : "NT"),
					LOBYTE(LOWORD(version)), HIBYTE(LOWORD(version)),
					HIWORD(version)&0x7FFF);
				OutputDebugString(tracebuf);
				wsprintf(tracebuf, "Console Size=(%d,%d), Window=(%d,%d) to (%d,%d)\n",
							info.dwSize.X, info.dwSize.Y,
							info.srWindow.Left, info.srWindow.Top,
							info.srWindow.Right, info.srWindow.Bottom);
				OutputDebugString(tracebuf);
			}
#endif
			CHECK(SetStdHandle(STD_OUTPUT_HANDLE, conout));
			CHECK(SetStdHandle(STD_ERROR_HANDLE, conerr));
			CHECK(SetConsoleActiveScreenBuffer(conout));
		}
		CHECK(SetConsoleTitle("Jove for Win32"));
		conin = GetStdHandle(STD_INPUT_HANDLE);
		/* conout = GetStdHandle(STD_OUTPUT_HANDLE); */

# ifdef MOUSE
		SetConsoleMode(conin,
			ENABLE_MOUSE_INPUT | ENABLE_WINDOW_INPUT);
		MouseOn();
# else
		SetConsoleMode(conin, ENABLE_WINDOW_INPUT);
# endif /* !MOUSE */
		SetConsoleMode(conout,
			ENABLE_PROCESSED_OUTPUT | ENABLE_WRAP_AT_EOL_OUTPUT);
		SetFileApisToOEM();
		SetConsoleCtrlHandler(ctrlHandler, TRUE);
	} else {
		CHECK(SetStdHandle(STD_OUTPUT_HANDLE, old_stdout));
		CHECK(SetStdHandle(STD_ERROR_HANDLE, old_stderr));
		CHECK(SetConsoleActiveScreenBuffer(old_stdout));
		CloseHandle(conout);
		CloseHandle(conerr);
		SetConsoleTextAttribute(old_stdout, old_attributes);
		conin = conout = conerr = NULL;
# ifdef MOUSE
		MouseOff();
# endif
		SetConsoleCtrlHandler(ctrlHandler, FALSE);
	}
}

void
ttsize()
{
	/* ??? We really ought to wait until the screen is big enough:
	 * at least three lines high (one line each for buffer, mode,
	 * and message) and at least twelve columns wide (eight for
	 * line number, one for content, two for overflow indicators,
	 * and one blank at end).
	 */
	CONSOLE_SCREEN_BUFFER_INFO info;
	GetConsoleScreenBufferInfo(conout, &info);
	maxpos.X = info.srWindow.Right-info.srWindow.Left+1;
	maxpos.Y = info.srWindow.Bottom-info.srWindow.Top+1;
	if (maxpos.X != info.dwSize.X || maxpos.Y != info.dwSize.Y) {
		CHECK(SetConsoleScreenBufferSize(conout, maxpos));
		curpos.X = curpos.Y = 0;
	} else {
		curpos = info.dwCursorPosition;
	}
	CO = maxpos.X;
	if (CO > MAXCOLS)
		CO = MAXCOLS;
	LI = maxpos.Y;
	ILI = LI - 1;
}

#ifdef DEBUG
private char *
formatChar(char c)
{
	static char cbuf[6];

	if (c >= ' ' && c <= '~')
		wsprintf(cbuf, "%c", c);
	else
		wsprintf(cbuf, "\\%03o", (unsigned char)c);
	return cbuf;
}

private char *
formatEvent(INPUT_RECORD* event)
{
	static char buffer[128];

	switch (event->EventType) {
	  case KEY_EVENT:
		wsprintf(buffer, "KEY%s '%s%s%s%s'[%d times] Scan=%d Keycode=%d\n",
			event->Event.KeyEvent.bKeyDown?"DOWN":"UP",
			(event->Event.KeyEvent.dwControlKeyState &
				(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED) ? "ALT-" : ""),
			(event->Event.KeyEvent.dwControlKeyState &
				(RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED) ? "CTRL-" : ""),
			(event->Event.KeyEvent.dwControlKeyState &
				(SHIFT_PRESSED) ? "SHIFT-" : ""),
			formatChar(event->Event.KeyEvent.uChar.AsciiChar),
			event->Event.KeyEvent.wRepeatCount,
			event->Event.KeyEvent.wVirtualScanCode,
			event->Event.KeyEvent.wVirtualKeyCode
			);
		break;
	  case MOUSE_EVENT:
		wsprintf(buffer, "MOUSE%s%s %s%s%s%s%s%s (%d,%d)\n",
				(event->Event.MouseEvent.dwEventFlags&MOUSE_MOVED ?
						" MOVED" : ""),
				(event->Event.MouseEvent.dwEventFlags&DOUBLE_CLICK?
						" DOUBLE_CLICK" : ""),
				(event->Event.MouseEvent.dwButtonState&1 ? "LEFT" : ""),
				(event->Event.MouseEvent.dwButtonState&2 ? "RIGHT" : ""),
				(event->Event.MouseEvent.dwButtonState&4 ? "MIDDLE" : ""),
				(event->Event.MouseEvent.dwControlKeyState &
					(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED) ? "ALT-" : ""),
				(event->Event.MouseEvent.dwControlKeyState &
					(RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED) ? "CTRL-" : ""),
				(event->Event.MouseEvent.dwControlKeyState &
					(SHIFT_PRESSED) ? "SHIFT-" : ""),
				event->Event.MouseEvent.dwMousePosition.X,
				event->Event.MouseEvent.dwMousePosition.Y
				);
		break;
	  case WINDOW_BUFFER_SIZE_EVENT:
		wsprintf(buffer, "RESIZE (%d,%d)\n",
			eventp->Event.WindowBufferSizeEvent.dwSize.Y,
			eventp->Event.WindowBufferSizeEvent.dwSize.X);
		break;
	  case MENU_EVENT:
		wsprintf(buffer, "MENU %d\n", eventp->Event.MenuEvent.dwCommandId);
		break;
	  case FOCUS_EVENT:
		wsprintf(buffer, "FOCUS %d\n", eventp->Event.FocusEvent.bSetFocus);
		break;
	  default:
		wsprintf(buffer, "UNKNOWN %d\n", eventp->EventType);
		break;
	}
	return buffer;
}
#endif /* DEBUG */

/* MapKeyEventToChars maps Console KEY_RECORDs into a string of characters.
 * The mapping rules are basically those of the IBM AT BIOS as modified for
 * use by JOVE (i.e. the result should agree with what a JOVE user would see
 * running the MS-DOS version of Jove).
 */
private int
MapKeyEventToChars(KEY_EVENT_RECORD* key, char *bptr)
{
	int cnt = 0;

	if (key->bKeyDown) {
		if (key->uChar.AsciiChar) {
			if ((key->dwControlKeyState &
				(RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED))
			&& key->uChar.AsciiChar == ' ')
			{
				bptr[cnt++] = '\0';
			} else if (key->dwControlKeyState &
				(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED))
			{
				bptr[cnt++] = PCNONASCII;
				if (key->wVirtualScanCode >= 2
				&& key->wVirtualScanCode <= 13)
				{
					/* Top row (numeric keys) */
					bptr[cnt++] = key->wVirtualScanCode + 118;
				} else {
					bptr[cnt++] = key->wVirtualScanCode;
				}
			} else {
				bptr[cnt++] = key->uChar.AsciiChar;
			}
		} else {
#			define ShiftSelect(plain, shifted, ctrled, alted) ( \
				(key->dwControlKeyState & SHIFT_PRESSED) \
					? (shifted) \
				: (key->dwControlKeyState & (RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED)) \
					? (ctrled) \
				: (key->dwControlKeyState & (RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED)) \
					? (alted) \
				: (plain))

			switch (key->wVirtualKeyCode) {
			  case VK_F1: case VK_F2: case VK_F3: case VK_F4: case VK_F5:
			  case VK_F6: case VK_F7: case VK_F8: case VK_F9: case VK_F10:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = key->wVirtualKeyCode - VK_F1
					+ ShiftSelect(59, 84, 94, 104);
				break;
			  case VK_F11: case VK_F12:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = key->wVirtualKeyCode - VK_F11
					+ ShiftSelect(133, 135, 137, 139);
				break;
			  case '1': case '2': case '3': case '4': case '5':
			  case '6': case '7': case '8': case '9': case '0':
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = key->wVirtualScanCode + 118;
				break;
			  case VK_HOME:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(71, 171, 119, 151);
				break;
			  case VK_UP:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(72, 171, 141, 152);
				break;
			  case VK_PRIOR:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(73, 172, 132, 153);
				break;
			  case VK_SUBTRACT:	/* KEYPAD- */
				bptr[cnt++] = PCNONASCII;
				if (key->dwControlKeyState &
						(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED))
				{
					if (key->dwControlKeyState & SHIFT_PRESSED) {
						bptr[cnt++] = (unsigned char)174;
					} else {
						bptr[cnt++] = 74;
					}
				} else {
					bptr[cnt++] = (unsigned char)142;
				}
				break;
			  case VK_LEFT:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(75, 175, 115, 155);
				break;
			  case VK_RIGHT:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(77, 177, 116, 157);
				break;
			  case VK_ADD:	/* KEYPAD+ */
				bptr[cnt++] = PCNONASCII;
				if (key->dwControlKeyState &
						(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED))
				{
					if (key->dwControlKeyState & SHIFT_PRESSED) {
						bptr[cnt++] = (unsigned char)178;
					} else {
						bptr[cnt++] = 78;
					}
				} else {
					bptr[cnt++] = (unsigned char)144;
				}
				break;
			  case VK_END:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(79, 179, 117, 159);
				break;
			  case VK_DOWN:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(80, 180, 145, 160);
				break;
			  case VK_NEXT:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(81, 181, 118, 161);
				break;
			  case VK_INSERT:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = ShiftSelect(82, 182, 146, 162);
				break;
			  case VK_DELETE:
				if (key->dwControlKeyState & SHIFT_PRESSED) {
					bptr[cnt++] = PCNONASCII;
					bptr[cnt++] = (unsigned char)183;
				} else if (key->dwControlKeyState &
					(RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED))
				{
					bptr[cnt++] = PCNONASCII;
					bptr[cnt++] = (unsigned char)147;
				} else if (key->dwControlKeyState &
					(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED))
				{
					bptr[cnt++] = PCNONASCII;
					bptr[cnt++] = (unsigned char)163;
				} else {
					bptr[cnt++] = '\177';	/* Mapped to ASCII DEL */
				}
				break;
			  case VK_DIVIDE:	/* KEYPAD/ */
				bptr[cnt++] = PCNONASCII;
				if (key->dwControlKeyState &
						(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED))
				{
					bptr[cnt++] = (unsigned char)164;
				} else {
					bptr[cnt++] = (unsigned char)149;
				}
				break;
			  case VK_MULTIPLY:	/* KEYPAD* */
				bptr[cnt++] = PCNONASCII;
				if (key->dwControlKeyState &
						(RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED))
				{
					bptr[cnt++] = 55;
				} else {
					bptr[cnt++] = (unsigned char)150;
				}
				break;
			  case VK_NUMPAD5:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = (unsigned char)143;
				break;
			  case VK_PRINT:	/* PRINT SCREEN */
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = 113;
				break;
			  case VK_ESCAPE:
				if (key->dwControlKeyState & (RIGHT_ALT_PRESSED|LEFT_ALT_PRESSED)) {
					bptr[cnt++] = PCNONASCII;
					bptr[cnt++] = key->wVirtualScanCode;
				} else {
					bptr[cnt++] = '\033';	/* ASCII ESC */
				}
				break;
			  case 'C':	/* PRINT SCREEN */
				if (key->dwControlKeyState & (RIGHT_CTRL_PRESSED|LEFT_CTRL_PRESSED)) {
					bptr[cnt++] = '\003';	/* Ctrl-C : remapped in Windows 95 */
				} else {
					bptr[cnt++] = PCNONASCII;
					bptr[cnt++] = key->wVirtualScanCode;
				}
				break;
			  case VK_CAPITAL:
			  case VK_SHIFT:
			  case VK_CONTROL:
			  case VK_MENU:
			  case VK_NUMLOCK:
			  case VK_SCROLL:
				/* Modifiers - ignore them. */
				break;
			  default:
				bptr[cnt++] = PCNONASCII;
				bptr[cnt++] = key->wVirtualScanCode;
				break;
			}
#			undef ShiftSelect
		}
	}
	return cnt;
}

int
getInputEvents(char *bp, int size)
{
	int nchars = 0;

	if (eventp >= in_event+nevents) {
		if (!ReadConsoleInput(conin, in_event, NCHARS, &nevents)) {
			complain("ReadConsole failed (shouldn't happen)");
			/* NOTREACHED */
		}
		eventp = in_event;
	}
	while (eventp < in_event+nevents) {
		switch (eventp->EventType) {
		  case KEY_EVENT:
			nchars += MapKeyEventToChars(&eventp->Event.KeyEvent, bp+nchars);
			if (--eventp->Event.KeyEvent.wRepeatCount == 0)
				eventp += 1;
			break;
		  case MOUSE_EVENT:
#ifdef MOUSE
			/* We are only interested in hits */
			if (eventp->Event.MouseEvent.dwButtonState != 0) {
				bp[nchars++] = PCNONASCII;
				bp[nchars++] = '\xb0'+((char)eventp->Event.MouseEvent.dwButtonState&017);
				bp[nchars++] = (char)eventp->Event.MouseEvent.dwMousePosition.X;
				bp[nchars++] = (char)eventp->Event.MouseEvent.dwMousePosition.Y;
			}
#endif
			eventp += 1;
			break;

		  case WINDOW_BUFFER_SIZE_EVENT:
			maxpos = eventp->Event.WindowBufferSizeEvent.dwSize;
			eventp += 1;
			ResizePending = YES;
			break;
		  default:
			eventp += 1;
			break;
		}
		if (nchars >= size - 5)
			break;
	}
	return nchars;
}

BOOL
inputEventWaiting(int period)
{
#ifdef IPROCS
	return wait_for_any_input(period) == WAIT_OBJECT_0;
#else
	INPUT_RECORD evnt[10];
	PINPUT_RECORD pEvnt;
	DWORD cnt;
	static BOOL first=TRUE;

	if (first)
		return first=FALSE;	/* Force display on first call */

	if (eventp < in_event+nevents)
		return TRUE;	/* Already something in the queue */

	while (WaitForSingleObject(conin, period) == WAIT_OBJECT_0) {
		if (!PeekConsoleInput(conin, evnt
		, sizeof(evnt)/sizeof(evnt[0]), &cnt))
			break;

		/* Check if the waiting event is of interest. */
		for (pEvnt=evnt; pEvnt<evnt+cnt; ++pEvnt) {
			if ((pEvnt->EventType == KEY_EVENT
			  && pEvnt->Event.KeyEvent.bKeyDown)
			|| pEvnt->EventType == WINDOW_BUFFER_SIZE_EVENT
			|| pEvnt->EventType == MOUSE_EVENT)
				return TRUE;
		}
		/* If we reach here, the event(s) are uninteresting - chuck them. */
		if (!ReadConsoleInput(conin, evnt, cnt, &cnt))
			break;
	}
	return FALSE;
#endif
}

private void
SaveBufferFile(Buffer *b)
{
	Buffer *save_buf = curbuf;
	char title[80];

	if (b->b_fname) {
		wsprintf(title, "Save Jove buffer `%s'?", b->b_name);
		if (MessageBox(NULL, b->b_fname, title, MB_YESNO) != IDYES)
			return;
	} else {
		OPENFILENAME ofn;	/* common dialog box structure */
		char szFile[_MAX_PATH];	/* filename string */
		char szDirPath[_MAX_PATH];	/* filename string */

		/* Set the members of the OPENFILENAME structure. */
		memset(&ofn, 0, sizeof(ofn));
		ofn.lStructSize = sizeof(OPENFILENAME);
		szFile[0] = '\0';
		ofn.lpstrFile = szFile;
		ofn.nMaxFile = _MAX_PATH;;
		ofn.lpstrFilter =	"All (*.*)\0*.*\0"
							"C source (*.c;*.cpp;*.cxx)\0*.c;*.cpp;*,cxx\0"
							"C header (*.h;*.hpp;*.hxx)\0*.h;*.hpp;*.hxx\0";
		ofn.lpstrInitialDir = szDirPath;
		ofn.lpstrTitle = title;
		wsprintf(title, "Save Jove buffer `%s' as:", b->b_name);

		getcwd(szDirPath, _MAX_PATH-1);
		ofn.Flags = OFN_SHOWHELP | OFN_OVERWRITEPROMPT |
			OFN_PATHMUSTEXIST;
		if (!GetSaveFileName(&ofn))
			return;

		setfname(b, ofn.lpstrFile);
	}

	/* Still here? Save the file, then. */
	SetBuf(b);
	SaveFile();
	SetBuf(save_buf);
}

private void
MessageCloseFiles()
{
	/* We use a static buffer pointer so that we can detect if we
	 * have been re-entered.  If so, we do nothing.  This can happen
	 * if the user takes too long to respond).
	 */
	static Buffer *b;

	if (b == NULL)
		for (b = world; b != NULL; b = b->b_next)
			if (b->b_type != B_SCRATCH
			&& b->b_type == B_FILE
			&& IsModified(b))
				SaveBufferFile(b);
}

/* Control interrupt handler deals with CTRL-BREAK and exit requests */
private BOOL WINAPI
ctrlHandler(DWORD type)
{
	switch(type) {
	  case CTRL_C_EVENT:
		return TRUE;	/* Ignore, keyboard handler will see it. */
	  case CTRL_BREAK_EVENT:
		return FALSE;
	  case CTRL_CLOSE_EVENT:
	  case CTRL_LOGOFF_EVENT:
	  case CTRL_SHUTDOWN_EVENT:
		MessageCloseFiles();
		break;
	}
	return FALSE;	/* Carry on */
}

private WORD
	c_attr = 0x07;	/* current attribute white on black */

int
	Txattr = 0x07,	/* VAR: text-attribute (white on black) */
	Mlattr = 0x70,	/* VAR: mode-line-attribute (black on white) */
	Hlattr = 0x10;	/* VAR: highlight-attribute */

#define c_row curpos.Y		/* current row */
#define c_col curpos.X		/* current column */

#define cur_mov(r, c)	{ \
		curpos.X = (c); curpos.Y = (r); \
		CHECK(SetConsoleCursorPosition(conout, curpos)); \
	}
#define setcolor(c)	{ \
		c_attr = (c); \
		CHECK(SetConsoleTextAttribute(conout, c_attr)); \
	}

/* WIN32 calls aren't cheap, so we buffer output */

private char displaybuf[1024];
private DWORD bufpos = 0;

/* getLastErrorString is a WIN32 helper function that returns a description
 * of the error code that GetLastError() returns.
 * Implementation note: we use a mode of operation whereby FormatMessage allocates
 * a string large enough; the returned value will be valid until the function is
 * called again.
 */
private char *reason;

private void
freeReason(void)
{
	if (reason) {
		VirtualFree((PVOID)reason, 0, MEM_RELEASE);
		reason = NULL;
	}
}

char *
getLastErrorString()
{
	static BOOL cleanupRegistered;
	char *ptr;

	if (!cleanupRegistered) {
		atexit(freeReason);
		cleanupRegistered = TRUE;
	}
	freeReason();
	FormatMessage(FORMAT_MESSAGE_FROM_SYSTEM|FORMAT_MESSAGE_ALLOCATE_BUFFER,
				NULL, GetLastError(), MAKELANGID(LANG_NEUTRAL, SUBLANG_NEUTRAL),
				(char*)&reason, 0, NULL);
	if (((ptr = strchr(reason, '\r')) != NULL)
	|| ((ptr = strchr(reason, '\n')) != NULL))
		*ptr = '\0';	/* Trim off carriage control */
	return reason;
}

private void
ConsoleFail(char *fdef)
{
	char why[200];
	char *reason = getLastErrorString();

	wsprintf(why, "Console function \"%s\" failed: %s", fdef, reason);
	if (MessageBox(NULL, getLastErrorString(), why, MB_OKCANCEL) == IDCANCEL)
		abort();
}

private void
scr_win(int no, int ulr, int ulc, int lrr, int lrc)
{
	SMALL_RECT r, clip;
	COORD whereto;
	CHAR_INFO charinfo;

	clip.Top = ulr;
	clip.Left = ulc;
	clip.Bottom = lrr;
	clip.Right = lrc;
	whereto.X = ulc;
	r.Left = ulc;
	r.Right = lrc;
	if (no > 0) {
		r.Top = ulr + no;
		r.Bottom = lrr;
		whereto.Y = ulr;
	} else {
		r.Top = ulr;
		r.Bottom = lrr + no;
		whereto.Y = ulr - no;
	}

	charinfo.Char.AsciiChar = ' ';
	charinfo.Attributes = c_attr;

	CHECK(ScrollConsoleScreenBuffer(conout, &r, &clip, whereto, &charinfo));

	/* If the scrolled region is less than half of the clip area, there will
	 * be an unscrolled area in the middle - it must be filled with space.
	 */
	if (no > 0) {
		whereto.Y += (r.Bottom - r.Top + 1);
		while (whereto.Y < r.Top) {
			DWORD written;

			CHECK(FillConsoleOutputCharacter(conout, ' ', lrc-ulc+1, whereto, &written));
			whereto.Y += 1;
		}
	} else {
		while (--whereto.Y > r.Bottom) {
			DWORD written;

			CHECK(FillConsoleOutputCharacter(conout, ' ', lrc-ulc+1, whereto, &written));
		}
	}
}

void
i_lines(top, bottom, num)
int top, bottom, num;
{
	scr_win(-num, top, 0, bottom, CO-1);
}

void
d_lines(top, bottom, num)
int top, bottom, num;
{
	scr_win(num, top, 0, bottom, CO-1);
}

void
clr_page()
{
	DWORD written;

	SO_off();
	cur_mov(0, 0);
	CHECK(FillConsoleOutputCharacter(conout, ' ', maxpos.X*maxpos.Y, curpos, &written));
	CHECK(FillConsoleOutputAttribute(conout, c_attr, maxpos.X*maxpos.Y, curpos, &written));
}

void
flushscreen()
{
	if (bufpos != 0) {
		DWORD written;

		CHECK(WriteConsole(conout, displaybuf, bufpos, &written, NULL));
		if (written != bufpos) {
			byte_move(displaybuf + written, displaybuf, bufpos - written);
		}
		bufpos = 0;
	}
}

void
clr_eoln()
{
	DWORD written;

	CHECK(FillConsoleOutputCharacter(conout, ' ', CO-c_col, curpos, &written));
	CHECK(FillConsoleOutputAttribute(conout, c_attr, CO-c_col, curpos, &written));
}

void
dobell(n)	/* declared in term.h */
int	n;
{
	MessageBeep(n);
}

void
ResizeWindow()
{
	/* Must update window size to eliminate those ugly scroll bars */
	SMALL_RECT newsize;

	newsize.Top = newsize.Left = 0;
	newsize.Right = maxpos.X-1;
	newsize.Bottom = maxpos.Y-1;
	/* Note: the following fails with "invalid address". If you can figure out why, please fix it. */
	SetConsoleWindowInfo(conout, TRUE, &newsize);
}

/* scr_putchar: put char on screen.  Declared in fp.h */

/* char is subject to default argument promotions */
void
scr_putchar(char c)
{
	if (bufpos >= sizeof(displaybuf))
		flushscreen();
	displaybuf[bufpos++] = c;
	switch (c) {
	case LF:
		c_row += 1;
		break;
	case CTL('M'): /* CR */
		c_col = 0;
		break;
	case BS:
		if (c_col > 0)
			c_col -= 1;
		break;
	case CTL('G'):	/* ??? is this ever used? */
		dobell(1);
		break;
	default:
		if (++c_col > CO-1) {
			c_col = 0;
			c_row += 1;
		}
		break;
	}
}

/* No cursor optimization on an WIN32, this simplifies things a lot.
 * Think about it: it would be silly!
 */

void
Placur(line, col)
int	line,
	col;
{
	flushscreen();
	cur_mov(line, col);
	CapCol = col;
	CapLine = line;
}

private bool
	doing_so = NO,
	doing_us = NO;

private void
doattr()
{
	flushscreen();
	setcolor((doing_so? Mlattr : Txattr) ^ (doing_us? Hlattr : 0));
}

void
SO_effect(f)
bool f;
{
	doing_so = f;
	doattr();
}

void
US_effect(f)
bool	f;
{
	doing_us = f;
	doattr();
}

int
FatalErrorMessage(char* str)
{
	return MessageBox(NULL, str, NULL, MB_YESNO) == IDYES? 'y' : 'n';
}

#endif /* WIN32 */
