/* --- Copyright University of Sussex 1997. All rights reserved. ----------
 * File:			C.win32/extern/src/io.c
 * Purpose:			File I/O
 * Author:			Robert John Duncan, Nov 10 1995 (see revisions)
 */

#include "popcore.h"
#include "../unicode/encoding.h"

#define ENCODER(pf) (((CODING_PAIR*)pf->encoding)->encode)
#define DECODER(pf) (((CODING_PAIR*)pf->encoding)->decode)

/*
#define MTIO
	Define this for multi-threaded I/O, i.e. where each file has its own
	thread for reading and writing, so that the main thread can be
	interrupted. Without that, once the main thread has committed to an
	I/O operation it will hang until the operation completes.
*/

HANDLE pop_in_console_read;
	/* set to the console input handle while reading */

static DWORD pf_error(POP_FILE* pf) {
	pf->flags |= POP_FILE_ERROR;
	return pf->error = GetLastError();
}

__inline
static void pf_read_done(POP_FILE* pf, DWORD nbytes) {
	/* update file structure after read of nbytes */
	if (pf->overlapped && GetFileType(pf->handle) == FILE_TYPE_DISK)
		/* advance file pointer by hand */
		pf->overlapped->Offset += nbytes;
	pf->nbytes += nbytes;
	pf->ptr = pf->buffer;
}

__inline
static void pf_write_done(POP_FILE* pf, DWORD nbytes) {
	/* update file structure after write of nbytes */
	if (pf->overlapped && GetFileType(pf->handle) == FILE_TYPE_DISK)
		/* advance file pointer by hand */
		pf->overlapped->Offset += nbytes;
	if (nbytes == pf->nbytes) {
		pf->nbytes = 0;
		pf->ptr = pf->buffer;
	}
	else if (nbytes > 0) {
		/* partial write */
		pf->nbytes -= nbytes;
		MoveMemory(pf->buffer, pf->buffer + nbytes, pf->nbytes);
		pf->ptr = pf->buffer + pf->nbytes;
	}
}

__inline
static BOOL pf_read(POP_FILE* pf) {
	DWORD nbytes;
	if (!ReadFile(pf->handle, pf->buffer + pf->nbytes, pf->size - pf->nbytes,
				  &nbytes, pf->overlapped))
		switch (pf_error(pf)) {
			case ERROR_BROKEN_PIPE:
			case ERROR_HANDLE_EOF:
				/* not real errors */
				pf->flags &= ~POP_FILE_ERROR;
		}
	pf_read_done(pf, nbytes);
	return !(pf->flags & POP_FILE_ERROR);
}

__inline
static BOOL pf_write(POP_FILE* pf) {
	DWORD nbytes;
	if (!WriteFile(pf->handle, pf->buffer, pf->nbytes, &nbytes, pf->overlapped))
		pf_error(pf);
	pf_write_done(pf, nbytes);
	return !(pf->flags & POP_FILE_ERROR);
}

__inline
static BOOL pf_wait(POP_FILE* pf) {
	switch (WaitForMultipleObjects(2, pf->io_wait, FALSE, pf->timeout)) {
		case WAIT_OBJECT_0:
			/* I/O complete */
			if (pf->overlapped) {
				DWORD nbytes = 0;
				if (!GetOverlappedResult(pf->handle, pf->overlapped, &nbytes,
										 FALSE)) {
					if (pf_error(pf) == ERROR_HANDLE_EOF)
						/* not a real error */
						pf->flags &= ~POP_FILE_ERROR;
				}
				if (pf->flags & POP_FILE_READING)
					pf_read_done(pf, nbytes);
				else
					pf_write_done(pf, nbytes);
			}
			pf->flags &= ~(POP_FILE_READING|POP_FILE_WRITING);
			return !(pf->flags & POP_FILE_ERROR);
		case WAIT_OBJECT_0+1:
			pf->flags |= POP_FILE_INTERRUPT;
			return FALSE;
		case WAIT_TIMEOUT:
			pf->flags |= POP_FILE_TIMEOUT;
			return FALSE;
		default:
			pf_error(pf);
			return FALSE;
	}
}

#ifdef MTIO
/* This is the thread code attached to each file */
static int io_thread(POP_FILE* pf) {
	while (WaitForSingleObject(pf->io_todo, INFINITE) == WAIT_OBJECT_0) {
		if (pf->flags & POP_FILE_READING)
			pf_read(pf);
		else if (pf->flags & POP_FILE_WRITING)
			pf_write(pf);
		else
			/* normal termination */
			return 0;
		SetEvent(pf->io_done);
	}
	/* abnormal termination */
	pf_error(pf);
	return -1;
}
#endif	/* MTIO */

static BOOL file_read(POP_FILE* pf) {
	/* try overlapped read first */
	if (pf->overlapped) {
		if (!(pf->flags & POP_FILE_READING)) {
			/* start new input transaction */
			BOOL status = pf_read(pf);
			if (status || pf->error != ERROR_IO_PENDING)
				return status;
			/* async read in progress */
			pf->flags &= ~POP_FILE_ERROR;
			pf->flags |= POP_FILE_READING;
		}
		return pf_wait(pf);
	}
	else {
	/* non-overlapped treatment varies according to MTIO */
#ifdef MTIO
		if (!(pf->flags & POP_FILE_READING)) {
			pf->flags |= POP_FILE_READING;
			SetEvent(pf->io_todo);
		}
		return pf_wait(pf);
#else
		return pf_read(pf);
#endif	/* MTIO */
	}
}

static BOOL file_write(POP_FILE* pf) {
	/* try overlapped write first */
	if (pf->overlapped) {
		if (!(pf->flags & POP_FILE_WRITING)) {
			/* start new output transaction */
			BOOL status = pf_write(pf);
			if (status || pf->error != ERROR_IO_PENDING)
				return status;
			/* async write in progress */
			pf->flags &= ~POP_FILE_ERROR;
			pf->flags |= POP_FILE_WRITING;
		}
		return pf_wait(pf);
	}
	else {
	/* non-overlapped treatment varies according to MTIO */
#ifdef MTIO
		if (!(pf->flags & POP_FILE_WRITING)) {
			pf->flags |= POP_FILE_WRITING;
			SetEvent(pf->io_todo);
		}
		return pf_wait(pf);
#else
		return pf_write(pf);
#endif	/* MTIO */
	}
}

static BOOL file_flush(POP_FILE* pf) {
	DWORD nbytes;
	if ((pf->flags & POP_FILE_WRITING) && !file_write(pf))
		return FALSE;
	nbytes = pf->nbytes;
	while (nbytes != 0 && file_write(pf) && pf->nbytes < nbytes)
		nbytes = pf->nbytes;
	return nbytes == 0;
}

static DWORD file_seek(POP_FILE* pf, LONG nbytes, DWORD method) {
	DWORD pos;
	if (method == FILE_CURRENT && pf->overlapped) {
		/* file pointer won't be properly set */
		nbytes += pf->overlapped->Offset;
		method = FILE_BEGIN;
	}
	pos = SetFilePointer(pf->handle, nbytes, NULL, method);
	if (pos == 0xffffffff)
		pf_error(pf);
	else if (pf->overlapped)
		pf->overlapped->Offset = pos;
	return pos;
}

static void file_close(POP_FILE* pf) {
	if (pf->io_done) {
		CloseHandle(pf->io_done);
		pf->io_done = 0;
	}
	if (pf->io_todo) {
		CloseHandle(pf->io_todo);
		pf->io_todo = 0;
	}
	if (pf->overlapped) {
		pop_free(pf->overlapped);
		pf->overlapped = NULL;
	}
	if (pf->buffer) {
		pop_free(pf->buffer);
		pf->buffer = NULL;
	}
	pf->flags = 0;
}

static BOOL file_init(POP_FILE* pf) {
	if (!(pf->buffer = pop_alloc(1024))) {
		file_close(pf);
		pf_error(pf);
		return FALSE;
	}
	pf->size = 1024;
	pf->nbytes = 0;
	pf->ptr = pf->buffer;
	pf->timeout = INFINITE;
	if (pf->overlapped) {
		/* create manual-reset event for overlapped I/O */
		if (!(pf->io_done = CreateEvent(NULL, TRUE, FALSE, NULL))) {
			file_close(pf);
			pf_error(pf);
			return FALSE;
		}
		pf->overlapped->hEvent = pf->io_done;
	}
	else {
#ifdef MTIO
		DWORD thread_id;
		if (!(pf->io_todo = CreateEvent(NULL, FALSE, FALSE, NULL))
		||	!(pf->io_done = CreateEvent(NULL, FALSE, FALSE, NULL))) {
			file_close(pf);
			pf_error(pf);
			return FALSE;
		}
		pf->thread = CreateThread(
			NULL,
			4096,
			(LPTHREAD_START_ROUTINE)io_thread,
			pf,
			0,
			&thread_id);
		if (!pf->thread) {
			file_close(pf);
			pf_error(pf);
			return FALSE;
		}
#endif	/* MTIO */
	}
	pf->io_wait[0] = pf->io_done;
	pf->io_wait[1] = pop_ast_queue_non_empty;
	pf->flags = POP_FILE_READY;
	return TRUE;
}

static BOOL prepare_file_read(POP_FILE* pf) {
	if (!(pf->flags & POP_FILE_READY)) {
		if (!file_init(pf)) return FALSE;
	}
	else if (pf->flags & POP_FILE_OUTPUT) {
		/* flush pending output */
		if (!file_flush(pf)) return FALSE;
		pf->flags &= ~POP_FILE_OUTPUT;
	}
	else if (pf->flags & POP_FILE_READING) {
		/* complete pending input */
		if (!file_read(pf)) return FALSE;
	}
	pf->flags &= ~(POP_FILE_INTERRUPT|POP_FILE_TIMEOUT);
	return TRUE;
}

static BOOL prepare_file_write(POP_FILE* pf) {
	if (!(pf->flags & POP_FILE_READY)) {
		if (!file_init(pf)) return FALSE;
	}
	else if (!(pf->flags & POP_FILE_OUTPUT)) {
		/* check for input pending */
		if (pf->flags & POP_FILE_READING)
			/* wait for input to complete */
			if (!file_read(pf)) return FALSE;
		/* discard any input in the buffer */
		if (pf->nbytes > 0)
			/* rewind over input not yet read */
			if (file_seek(pf, -(int)pf->nbytes, FILE_CURRENT) == 0xffffffff)
				return FALSE;
		pf->nbytes = 0;
		pf->ptr = pf->buffer;
	}
	else if (pf->flags & POP_FILE_WRITING) {
		/* wait for output to complete */
		if (!file_write(pf)) return FALSE;
	}
	pf->flags &= ~(POP_FILE_INTERRUPT|POP_FILE_TIMEOUT);
	pf->flags |= POP_FILE_OUTPUT;
	return TRUE;
}

POP_FILE* pop_file_create(HANDLE* handle, BOOL overlapped) {
	POP_FILE* pf;
	OVERLAPPED* ovl;
	if (overlapped && !(ovl = pop_alloc(sizeof(OVERLAPPED))))
		return NULL;
	else if (!(pf = pop_alloc(sizeof(POP_FILE)))) {
		if (overlapped) pop_free(ovl);
		return NULL;
	}
	ZeroMemory(pf, sizeof(POP_FILE));
	pf->handle = handle;
	if (overlapped)  {
		ZeroMemory(ovl, sizeof(OVERLAPPED));
		pf->overlapped = ovl;
	}
	return pf;
}

BOOL pop_file_flush(POP_FILE* pf) {
	pf->timeout = INFINITE;
	if (!(pf->flags & POP_FILE_READY))
		return TRUE;
	else if (pf->flags & POP_FILE_OUTPUT)
		/* flush pending output */
		return file_flush(pf);
	else {
		if ((pf->flags & POP_FILE_READING) && !file_read(pf))
			return FALSE;
		/* discard buffered input */
		pf->nbytes = 0;
		pf->ptr = pf->buffer;
		return TRUE;
	}
}

BOOL pop_file_close(POP_FILE* pf) {
	/* complete pending I/O */
	if (!pop_file_flush(pf))
		return FALSE;
	/* terminate thread */
	if (pf->thread) {
		pf->flags &= ~(POP_FILE_READING|POP_FILE_WRITING);
		SetEvent(pf->io_todo);
		if (WaitForSingleObject(pf->thread, INFINITE) != WAIT_OBJECT_0
		||	!CloseHandle(pf->thread)) {
			pf_error(pf);
			return FALSE;
		}
		pf->thread = 0;
	}
	/* deallocate storage */
	file_close(pf);
	/* close file handle */
	if (pf->handle) {
		if (!CloseHandle(pf->handle)) {
			pf_error(pf);
			return FALSE;
		}
		pf->handle = 0;
	}
	/* invalidate file structure */
	pop_free(pf);
	return TRUE;
}

DWORD pop_file_seek(POP_FILE* pf, LONG nbytes, DWORD method) {
	DWORD pos;
	if (!(pf->flags & POP_FILE_READY)) {
		if (!file_init(pf)) return 0xffffffff;
	}
	else if (pf->flags & POP_FILE_OUTPUT) {
		/* flush pending output */
		if (!file_flush(pf)) return 0xffffffff;
	}
	else {
		if (pf->flags & POP_FILE_READING)
			/* complete pending input */
			if (!file_read(pf)) return 0xffffffff;
		/* account for any buffered input */
		if (method == 1)
			nbytes -= pf->nbytes;
		pf->ptr = pf->buffer;
		pf->nbytes = 0;
	}
	pos = file_seek(pf, nbytes, method == 0 ? FILE_BEGIN :
								method == 1 ? FILE_CURRENT :
											  FILE_END);
	return pos;
}

BOOL pop_file_start_overlapped_read(POP_FILE* pf) {
	if (pf->flags & POP_FILE_READING)
		/* read already in progress */
		return TRUE;
	else if (pf->flags != POP_FILE_INPUT_READY && !prepare_file_read(pf))
		return FALSE;
	else if (pf->io_done == NULL || (pf->flags & POP_FILE_ERROR))
		/* not overlapped or in error */
		return FALSE;
	else if (pf->nbytes > 0)
		/* input available */
		return TRUE;
	else {
		/* start non-blocking read */
		DWORD timeout = pf->timeout;
		pf->timeout = 0;
		file_read(pf);
		pf->timeout = timeout;
		return (pf->flags & POP_FILE_ERROR) == 0;
	}
}

HANDLE pop_file_is_overlapped(POP_FILE* pf) {
	/* return event signalling I/O completion */
	return pop_file_start_overlapped_read(pf) ? pf->io_done : NULL;
}

int pop_file_test_input(POP_FILE* pf) {
	if (pf->flags & POP_FILE_READING) {
		/* try and complete the read, but without blocking */
		DWORD timeout = pf->timeout;
		pf->timeout = 0;
		file_read(pf);
		pf->timeout = timeout;
	}
	return pf->flags == POP_FILE_INPUT_READY ? pf->nbytes : -1;
}

void pop_file_clear_input(POP_FILE* pf) {
	if (pop_file_test_input(pf) > 0) {
		/* discard input waiting */
		pf->nbytes = 0;
		pf->ptr = pf->buffer;
		pf->istate = 0;
	}
}

static DWORD read_encoded(POP_FILE* pf, WCHAR* addr, DWORD nchars,
						  X_TO_UNICODE decode) {
	/* nchars > 0 */
	UINT ilen, olen, count = nchars, flags = INPUT_INCOMPLETE;
	for (;;) {
		ilen = pf->nbytes, olen = count;
		decode(pf->ptr, &ilen, addr, &olen, &pf->istate, flags);
		pf->ptr += pf->nbytes - ilen;
		pf->nbytes = ilen;
		count -= olen;
		if (count == 0 || flags == 0)
			break;
		/* insufficient input: need to read ahead */
		if (ilen > 0)
			/* input ends in mid-character */
			MoveMemory(pf->buffer, pf->ptr, ilen);
		if (!file_read(pf))
			break;
		if (pf->nbytes == ilen) {
			/* nothing more read */
			if (ilen == 0) break;
			/* set decode flags to mop up what's left */
			flags = 0;
		}
		addr += olen;
	}
	return nchars - count;
}

static DWORD special_read_encoded(POP_FILE* pf, WCHAR* addr, DWORD nchars,
								  DWORD mode, X_TO_UNICODE decode) {
	/* nchars > 0 */
	WCHAR c;
	UINT state, ilen, olen, count = nchars, flags = INPUT_INCOMPLETE;
	for (;;) {
		ilen = pf->nbytes, olen = 1, state = pf->istate;
		decode(pf->ptr, &ilen, &c, &olen, &state, flags);
		if (olen == 0) {
			/* insufficient input: need to read ahead */
			if (flags == 0)
				/* already tried this: must be EOF */
				break;
			if (ilen > 0)
				/* input ends in mid-character */
				MoveMemory(pf->buffer, pf->ptr, ilen);
			pf->nbytes = ilen;
			pf->istate = state;
			if (!file_read(pf))
				break;
			if (pf->nbytes == ilen) {
				/* nothing more read */
				if (ilen == 0) break;
				/* set decode flags to mop up what's left */
				flags = 0;
			}
			continue;
		}
		if (c == '\r') {
			if (mode & POP_FILE_MODE_TEXT) {
				/* peek ahead for newline */
				UINT save_ilen = ilen, save_state = state;
				decode(pf->ptr + pf->nbytes - ilen, &ilen, &c, &olen, &state,
					flags);
				if (olen == 0 && flags != 0) {
					/*	insufficient input: need to read ahead but without
						losing the CR if the read is abandoned for any reason
					*/
					ilen = pf->nbytes;
					MoveMemory(pf->buffer, pf->ptr, ilen);
					if (!file_read(pf))
						break;
					if (pf->nbytes == ilen)
						/* nothing more read */
						flags = 0;
					continue;
				}
				if (c != '\n') {
					c = '\r';
					ilen = save_ilen;
					state = save_state;
				}
			}
		}
		else if (c == POP_FILE_EOF_CHAR) {
			if (mode & POP_FILE_MODE_TEXT)
				break;
		}
		pf->ptr += pf->nbytes - ilen;
		pf->nbytes = ilen;
		pf->istate = state;
		addr[0] = c;
		if (--count == 0 || (c == '\n' && (mode & POP_FILE_MODE_LINE)))
			break;
		++addr;
	}

	return nchars - count;
}

DWORD pop_file_read(POP_FILE* pf, BYTE* addr, DWORD nchars) {
	DWORD n, count = nchars;
	if (count == 0)
		return 0;
	else if (pf->flags != POP_FILE_INPUT_READY && !prepare_file_read(pf))
		return 0;
	pf->timeout = INFINITE;
	if (pf->encoding)
		return read_encoded(pf, (WCHAR*)addr, count, DECODER(pf));
	do {
		n = pf->nbytes;
		if (n == 0)	{
			if (!file_read(pf)) break;
			n = pf->nbytes;
			if (n == 0) break;
		}
		if (n > count) n = count;
		CopyMemory(addr, pf->ptr, n);
		pf->ptr += n;
		pf->nbytes -=n;
		addr += n;
		count -= n;
	} while (count > 0);
	/* return number of characters copied in */
	return nchars - count;
}

DWORD pop_file_special_read(POP_FILE* pf, BYTE* addr, DWORD nchars, DWORD mode,
							int timeout) {
	DWORD count = nchars;
	if (count == 0)
		return 0;
	else if (pf->flags != POP_FILE_INPUT_READY && !prepare_file_read(pf))
		return 0;
	pf->timeout = timeout >= 0 ? timeout  : INFINITE;
	if (pf->encoding)
		return special_read_encoded(pf, (WCHAR*)addr, count, mode, DECODER(pf));
	for (;;) {
		BYTE c;
		if (pf->nbytes == 0)
			if (!file_read(pf) || pf->nbytes == 0)
				break;
		c = pf->ptr[0];
		if (c == '\r') {
			if (mode & POP_FILE_MODE_TEXT) {
				/* peek ahead for newline */
				if (pf->nbytes == 1) {
					/*	need to read ahead, but without losing the CR if the
						read is abandoned for any reason
					*/
					pf->buffer[0] = '\r';
					if (!file_read(pf) || pf->nbytes == 1)
						/* nothing more to read */
						break;
				}
				if (pf->ptr[1] == '\n') {
					c = '\n';
					++pf->ptr;
					--pf->nbytes;
				}
			}
		}
		else if (c == POP_FILE_EOF_CHAR) {
			if (mode & POP_FILE_MODE_TEXT)
				break;
		}
		++pf->ptr, --pf->nbytes;
		addr[0] = c;
		if (--count == 0 || (c == '\n' && (mode & POP_FILE_MODE_LINE)))
			break;
		++addr;
	}

	/* return number of characters copied in */
	return nchars - count;
}

/*	console_prompt
	==============
	Print a prompt on the active console screen buffer, but only if it looks
	needed. That's in two cases:
	(a) there's a read in progress and the cursor's in column 0 (suggesting
		output's been done after an interrupt or timeout)
	(b) there's no read in progress, and this request for nbytes can't be
		satisfied from what's already in the buffer, remembering that a
		console read will always stop at a newline
*/
static void console_prompt(POP_FILE* pf, TCHAR* prompt, DWORD nbytes) {
	HANDLE conout;
	CONSOLE_SCREEN_BUFFER_INFO info;
	/* can't examine a file unless it's ready */
	if (!(pf->flags & POP_FILE_READY) && !file_init(pf))
		return;
	/* don't prompt if request can be satisfied */
	if (!(pf->flags & POP_FILE_READING))
		if (pf->nbytes >= nbytes
		|| (pf->nbytes > 0 && pf->ptr[pf->nbytes-1] == '\n'))
			return;
	/* get a handle on the active screen buffer */
	conout = pop_get_console_handle(TRUE);
	if (conout == INVALID_HANDLE_VALUE) return;
	/* output prompt unless read in progress and cursor beyond column 0 */
	if (!(pf->flags & POP_FILE_READING)
	|| (GetConsoleScreenBufferInfo(conout, &info)
			&& info.dwCursorPosition.X == 0))
		WriteConsole(conout, prompt, lstrlen(prompt), &nbytes, 0);
	/* close screen buffer handle */
	CloseHandle(conout);
}

DWORD pop_file_console_read(POP_FILE* pf, BYTE* addr, DWORD nbytes,
							DWORD mode, int timeout, TCHAR* prompt) {
	DWORD cmode;
	/* save current console mode */
	if (!GetConsoleMode(pf->handle, &cmode)) return 0;
	if (mode & POP_FILE_MODE_RAW) {
		/* set raw mode */
		if (cmode != 0 && !SetConsoleMode(pf->handle, 0)) return 0;
		/* read with no input translations */
		mode = 0;
	}
	else {
		/* set cooked mode */
		static DWORD cooked =
			ENABLE_LINE_INPUT|ENABLE_ECHO_INPUT|ENABLE_PROCESSED_INPUT;
		if ((cmode & cooked) != cooked
		&&  !SetConsoleMode(pf->handle, cmode|cooked))
			return 0;
		/* prompt if needed */
		if (prompt) console_prompt(pf, prompt, nbytes);
		/* read stops at NL */
		mode |= POP_FILE_MODE_LINE;
	}
	pop_in_console_read = pf->handle;
	nbytes = pop_file_special_read(pf, addr, nbytes, mode, timeout);
	pop_in_console_read = NULL;
	/* restore saved console mode */
	SetConsoleMode(pf->handle, cmode);

	return nbytes;
}

static DWORD write_encoded(POP_FILE* pf, WCHAR* addr, DWORD nchars, DWORD mode,
						   UNICODE_TO_X encode) {
	UINT ilen, isave, olen, osave, flags;
	ilen = nchars;	/* nchars > 0 */
	flags = mode & POP_FILE_MODE_16BIT ? 0 : BYTE_INPUT;
	for (;;) {
		isave = ilen, osave = olen = pf->size - pf->nbytes;
		encode(addr, &ilen, pf->ptr, &olen, &pf->ostate, flags);
		pf->ptr += olen;
		pf->nbytes += olen;
		if (ilen == 0)
			break;
		/* input not consumed: output buffer must be full */
		if (!file_write(pf) || pf->size - pf->nbytes == osave - olen)
			break;
		if (flags == BYTE_INPUT)
			/* addr is really a BYTE pointer */
			addr = (WCHAR*)((BYTE*)addr + (isave - ilen));
		else
			addr += isave - ilen;
	}
	return nchars - ilen;
}

static DWORD special_write_encoded(POP_FILE* pf, WCHAR* addr, DWORD nchars,
								   DWORD mode, UNICODE_TO_X encode) {
	/* nchars > 0 */
	DWORD count = nchars;
	BYTE* baddr = (BYTE*)addr;
	UINT ilen, olen, osave;
	for (;;) {
		WCHAR c = mode & POP_FILE_MODE_16BIT ? addr[0] : baddr[0];
		if (c == '\n' && (mode & POP_FILE_MODE_TEXT)) {
			/* NL -- add a preceding CR */
			UINT ostate = pf->ostate;
			ilen = 2, osave = olen = pf->size - pf->nbytes;
			encode(L"\r\n", &ilen, pf->ptr, &olen, &ostate, 0);
			if (ilen > 0) {
				/* no room in the output buffer: try a flush */
				if (!file_write(pf) || pf->size - pf->nbytes == osave - olen)
					break;
				continue;
			}
			pf->ostate = ostate;
			pf->ptr += olen;
			pf->nbytes += olen;
		}
		else {
			ilen = 1, osave = olen = pf->size - pf->nbytes;
			encode(&c, &ilen, pf->ptr, &olen, &pf->ostate, 0);
			pf->ptr += olen;
			pf->nbytes += olen;
			if (ilen == 1) {
				/* no room in the output buffer: try a flush */
				if (!file_write(pf) || pf->size - pf->nbytes == osave - olen)
					break;
				continue;
			}
		}
		if (c == '\n') {
			if (mode & POP_FILE_MODE_LINE)
				/* flush output after NL */
				if (!file_write(pf))
					break;
		}
		else if (c == POP_FILE_EOF_CHAR) {
			if (mode & POP_FILE_MODE_TEXT) {
				/* flush and terminate output */
				file_write(pf);
				break;
			}
		}
		if (--count == 0)
			break;
		++addr, ++baddr;
	}

	return nchars - count;
}

DWORD pop_file_write(POP_FILE* pf, BYTE* addr, DWORD nchars, DWORD mode) {
	DWORD n, count = nchars;
	if (count == 0)
		return 0;
	else if (pf->flags != POP_FILE_OUTPUT_READY && !prepare_file_write(pf))
		return 0;
	pf->timeout = INFINITE;
	if (pf->encoding)
		return write_encoded(pf, (WCHAR*)addr, nchars, mode, ENCODER(pf));
	do {
		n = pf->size - pf->nbytes;
		if (n == 0) {
			if (!file_write(pf)) break;
			n = pf->size - pf->nbytes;
			if (n == 0) break;
		}
		if (n > count) n = count;
		CopyMemory(pf->ptr, addr, n);
		pf->ptr += n;
		pf->nbytes += n;
		addr += n;
		count -= n;
	} while (count > 0);
	/* return number of characters copied out */
	return nchars - count;
}

DWORD pop_file_special_write(POP_FILE* pf, BYTE* addr, DWORD nchars,
							 DWORD mode, int timeout) {
	DWORD count = nchars;
	if (count == 0)
		return 0;
	else if (pf->flags != POP_FILE_OUTPUT_READY && !prepare_file_write(pf))
		return 0;
	pf->timeout = timeout >= 0 ? timeout : INFINITE;
	if (pf->encoding)
		return special_write_encoded(pf, (WCHAR*)addr, nchars, mode, ENCODER(pf));
	do {
		BYTE c = addr[0];
		if (c == '\n' && (mode & POP_FILE_MODE_TEXT)) {
			/* NL -- add a preceding CR */
			if (pf->size - pf->nbytes < 2)
				if (!file_write(pf) || pf->size - pf->nbytes < 2) break;
			*pf->ptr++ = '\r';
			++pf->nbytes;
		}
		else if (pf->size - pf->nbytes == 0) {
			if (!file_write(pf) || pf->size - pf->nbytes == 0) break;
		}
		*pf->ptr++ = c;
		++pf->nbytes;
		++addr;
		--count;
		if (c == '\n' && (mode & POP_FILE_MODE_LINE)) {
			/* flush output after NL */
			if (!file_write(pf)) break;
		}
		else if (c == POP_FILE_EOF_CHAR && (mode & POP_FILE_MODE_TEXT)) {
			/* flush and terminate output */
			file_write(pf);
			break;
		}
	} while (count > 0);
	/* return number of characters copied out */
	return nchars - count;
}

DWORD pop_file_console_write(POP_FILE* pf, BYTE* addr, DWORD nbytes,
							 DWORD mode, int timeout) {
	DWORD cmode;
	/* save current console mode */
	if (!GetConsoleMode(pf->handle, &cmode)) return 0;
	if (mode & POP_FILE_MODE_RAW) {
		/* set raw mode */
		if (cmode != 0 && !SetConsoleMode(pf->handle, 0)) return 0;
		/* write with no output translations */
		mode = 0;
	}
	else {
		/* set cooked mode */
		static DWORD cooked =
			ENABLE_PROCESSED_OUTPUT|ENABLE_WRAP_AT_EOL_OUTPUT;
		if ((cmode & cooked) != cooked
		&&  !SetConsoleMode(pf->handle, cmode|cooked))
			return 0;
	}
	nbytes = pop_file_special_write(pf, addr, nbytes, mode, timeout);
	/* always flush */
	if (pf->flags == POP_FILE_OUTPUT_READY && pf->nbytes > 0)
		file_flush(pf);
	/* restore saved console mode */
	SetConsoleMode(pf->handle, cmode);

	return nbytes;
}

	/*	Low-level read (bypassing files) for "sr_sys.p"
	*/
DWORD pop_read(HANDLE handle, BYTE *addr, DWORD nbytes) {
	ReadFile(handle, addr, nbytes, &nbytes, NULL);
	return nbytes;
}


/* --- Revision History ---------------------------------------------------
--- Robert Duncan, Apr  3 1997
		Fixed overlapped I/O to modify the overlapped offset only for disk
		files: character-type devices typically want this left alone (zero).
		Added pop_file_start_overlapped_read and pop_file_is_overlapped to
		support Poplog's style of asynchronous input.
--- Robert Duncan, Feb 27 1997
		Changes for device encoding
--- Robert Duncan, Apr 19 1996
		Uses new pop_get_console_handle for prompting
--- Robert Duncan, Mar 29 1996
		Console keyboard handle saved in pop_in_console_read while reading
		from the console for access during control event handling
 */
