/* --- Copyright University of Sussex 1994. All rights reserved. ----------
 * File:			C.win32/extern/src/device.c
 * Purpose:			Device I/O
 * Author:			Robert John Duncan, Jun 14 1994
 * Documentation:
 * Related Files:
 */

#include "popcore.h"

#define CTRL_Z '\x1a'


/***************************************************************************
*                                                                          *
*	Device Buffers                                                         *
*                                                                          *
***************************************************************************/

#define ALLOC_BUFFER(nbytes) \
	pop_alloc(POP_DEVICE_BUFFER_SIZE(nbytes))
#define FREE_BUFFER(buffer) \
	pop_free(buffer)

BOOL pop_alloc_device_buffers(POP_DEVICE *device, DWORD access)
	/*	Allocate from the buffer space device buffers appropriate to the
		DEVICE mode and ACCESS mode
	*/
{
	DWORD nbytes = device->mode == 0 ? 512 : 128;
	POP_DEVICE_BUFFER *in_buffer = NULL;
	POP_DEVICE_BUFFER *out_buffer = NULL;

	if (access != 1)
	{
		/* readable -- allocate input buffer */
		in_buffer = ALLOC_BUFFER(nbytes);
		if (in_buffer == NULL)
			return FALSE;
		in_buffer->size = nbytes;
		in_buffer->posn = in_buffer->count = 0;
	}
	if (access != 0)
	{
		/* writeable -- allocate output buffer */
		out_buffer = ALLOC_BUFFER(nbytes);
		if (out_buffer == NULL)
		{
			if (in_buffer != NULL)
				FREE_BUFFER(in_buffer);
			return FALSE;
		}
		out_buffer->size = nbytes;
		out_buffer->posn = out_buffer->count = 0;
	}
	device->in_buffer = in_buffer;
	device->out_buffer = out_buffer;

	return TRUE;
}


/***************************************************************************
*                                                                          *
*	Reading from a Device                                                  *
*                                                                          *
***************************************************************************/

static DWORD do_read(HANDLE handle, BYTE *addr, DWORD nbytes, DWORD *nread)
{
	DWORD status;
	if (!ReadFile(handle, addr, nbytes, nread, NULL))
		if (GetLastError() == ERROR_BROKEN_PIPE)
			/*	hmm -- broken pipe means all the write handles have been
				closed, but isn't that really end-of-file? */
			status = POP_DEVICE_STATUS_EOF;
		else
			status = POP_DEVICE_STATUS_ERROR;
	else if (*nread == 0)
		status = POP_DEVICE_STATUS_EOF;
	else
		status = 0;
	return status;
}

DWORD pop_read_device(POP_DEVICE *device, BYTE *addr, DWORD nbytes)
{
	POP_DEVICE_BUFFER *buffer = device->in_buffer;
	DWORD buffer_posn = buffer->posn;
	DWORD buffer_count = buffer->count;
	DWORD count = nbytes;

	while (count > 0)
	{
		DWORD n = buffer_count - buffer_posn;
		if (n == 0)
		{
			DWORD status;
			status = do_read(device->handle, buffer->data, buffer->size, &n);
			buffer_posn = 0;
			buffer->count = buffer_count = n;
			if (status != 0)
			{
				device->status |= status;
				if (status == POP_DEVICE_STATUS_ERROR)
					device->error_code = GetLastError();
				break;
			}
		}
		if (n > count) n = count;
		CopyMemory(addr, buffer->data + buffer_posn, n);
		addr += n;
		buffer_posn += n;
		count -= n;
	}

	buffer->posn = buffer_posn;
	/* return number of characters copied in */
	return nbytes - count;
}

DWORD pop_read_special_device(POP_DEVICE *device, BYTE *addr, DWORD nbytes)
{
	WORD mode = device->mode;
	POP_DEVICE_BUFFER *buffer = device->in_buffer;
	DWORD buffer_posn = buffer->posn;
	DWORD buffer_count = buffer->count;
	DWORD count = nbytes;
	BYTE c;

	while (count > 0)
	{
		if (buffer_posn == buffer_count)
		{
			/* read some more data into the buffer */
			DWORD status, nread;
			if ((mode & POP_DEVICE_MODE_CONSOLE) != 0)
				pop_set_console_mode(device, FALSE);
			status = do_read(device->handle, buffer->data, buffer->size,
				&nread);
			buffer_posn = 0;
			buffer->count = buffer_count = nread;
			if (status != 0)
			{
				device->status |= status;
				if (status == POP_DEVICE_STATUS_ERROR)
					device->error_code = GetLastError();
				break;
			}
		}
		c = buffer->data[buffer_posn++];
		if ((mode & POP_DEVICE_MODE_TEXT) != 0)
			if (c == '\r')
			{
				/* CR -- check for following NL */
				if (buffer_posn == buffer_count)
				{
					/*	need to read ahead, but without losing the CR if the
						read fails for any reason
					*/
					DWORD status, nread;
					if ((mode & POP_DEVICE_MODE_CONSOLE) != 0)
						pop_set_console_mode(device, FALSE);
					status = do_read(device->handle, buffer->data+1,
						buffer->size-1, &nread);
					buffer->count = buffer_count = nread+1;
					if (status == 0)
						buffer_posn = 1;
					else if (status == POP_DEVICE_STATUS_EOF)
					{
						buffer_posn = 0;
						buffer->data[0] = CTRL_Z;
					}
					else
					{
						buffer_posn = 0;
						buffer->data[0] = '\r';
						device->status |= status;
						if (status == POP_DEVICE_STATUS_ERROR)
							device->error_code = GetLastError();
						break;
					}
				}
				if (buffer->data[buffer_posn] == '\n')
				{
					c = '\n';
					buffer_posn++;
				}
			}
			else if (c == CTRL_Z)
			{
				/* ^Z -- treat as EOF */
				device->status |= POP_DEVICE_STATUS_EOF;
				--buffer_posn;
				break;
			}
		*addr++ = c;
		count--;
		if (c == '\n' && (mode & POP_DEVICE_MODE_LINE) != 0)
			break;
	}

	buffer->posn = buffer_posn;
	/* return number of characters copied in */
	return nbytes - count;
}

DWORD pop_test_input_device(POP_DEVICE *device)
	/*	Return the number of characters in the device input buffer
		[NB: should also test for extra characters waiting to be read]
	*/
{
	return device->in_buffer->count - device->in_buffer->posn;
}

void pop_clear_input_device(POP_DEVICE *device)
	/*	Clear the device input buffer
	*/
{
	device->in_buffer->posn = device->in_buffer->count = 0;
	if ((device->mode & POP_DEVICE_MODE_CONSOLE) != 0)
		FlushConsoleInputBuffer(device->handle);
}

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


/***************************************************************************
*                                                                          *
*	Writing to a Device                                                    *
*                                                                          *
***************************************************************************/

static DWORD do_write(HANDLE handle, BYTE *addr, DWORD nbytes, DWORD *nwritten)
{
	return WriteFile(handle, addr, nbytes, nwritten, NULL) ? 0
		: POP_DEVICE_STATUS_ERROR;
}

static void flush_input(POP_DEVICE *device)
{
	POP_DEVICE_BUFFER *buffer = device->in_buffer;
	LONG count = buffer->count - buffer->posn;
	if (count > 0)
		/* wind file pointer back to buffer posn */
		SetFilePointer(device->handle, -count, NULL, FILE_CURRENT);
	buffer->count = buffer->posn = 0;
}

DWORD pop_write_device(POP_DEVICE *device, BYTE *addr, DWORD nbytes)
{
	POP_DEVICE_BUFFER *buffer = device->out_buffer;
	DWORD buffer_posn = buffer->posn;
	DWORD buffer_count = buffer->count;
	DWORD count = nbytes;

	if (device->in_buffer != NULL)
		/* clear any buffered input */
		flush_input(device);
	while (count > 0)
	{
		DWORD n = buffer->size - buffer_count;
		if (n == 0)
		{
			DWORD status;
			status = do_write(device->handle, buffer->data + buffer_posn,
				buffer_count - buffer_posn, &n);
			if (status != 0)
			{
				buffer_posn += n;	/* indicates partial write */
				device->status |= status;
				if (status == POP_DEVICE_STATUS_ERROR)
					device->error_code = GetLastError();
				break;
			}
			n = buffer_count;	/* == buffer->size */
			buffer_posn = buffer_count = 0;
		}
		if (n > count) n = count;
		CopyMemory(buffer->data + buffer_count, addr, n);
		addr += n;
		buffer_count += n;
		count -= n;
	}

	if (buffer_posn == buffer_count)
		buffer->posn = buffer->count = 0;
	else
	{
		buffer->posn = buffer_posn;
		buffer->count = buffer_count;
	}
	/* return number of characters copied out */
	return nbytes - count;
}

DWORD pop_write_special_device(POP_DEVICE *device, BYTE *addr, DWORD nbytes)
{
	WORD mode = device->mode;
	POP_DEVICE_BUFFER *buffer = device->out_buffer;
	DWORD buffer_posn = buffer->posn;
	DWORD buffer_count = buffer->count;
	DWORD buffer_size = buffer->size;
	DWORD count = nbytes, flush = 0, status, nwritten;
	BYTE c;

	if (device->in_buffer != NULL)
		/* clear any buffered input */
		flush_input(device);
	while (count > 0)
	{
		if (buffer_count == buffer_size)
		{
			/* buffer full -- write out what's there */
			if ((mode & POP_DEVICE_MODE_CONSOLE) != 0)
				pop_set_console_mode(device, TRUE);
			status = do_write(device->handle, buffer->data + buffer_posn,
				buffer_count - buffer_posn, &nwritten);
			flush = 0;
			if (status != 0)
			{
				buffer_posn += nwritten;	/* indicates partial write */
				device->status |= status;
				if (status == POP_DEVICE_STATUS_ERROR)
					device->error_code = GetLastError();
				break;
			}
			buffer_posn = buffer_count = 0;
		}
		c = *addr++;
		if (c == '\n' && (mode & POP_DEVICE_MODE_TEXT) != 0)
		{
			/* NL -- add a preceding CR */
			buffer->data[buffer_count++] = '\r';
			if (buffer_count == buffer_size)
			{
				/*	buffer full -- write out what's there, without
					splitting the CR/NL pair
				*/
				buffer_count--;
				if ((mode & POP_DEVICE_MODE_CONSOLE) != 0)
					pop_set_console_mode(device, TRUE);
				status = do_write(device->handle, buffer->data + buffer_posn,
					buffer_count - buffer_posn, &nwritten);
				flush = 0;
				if (status != 0)
				{
					buffer_posn += nwritten;	/* partial write */
					device->status |= status;
					if (status == POP_DEVICE_STATUS_ERROR)
						device->error_code = GetLastError();
					break;
				}
				buffer_posn = buffer_count = 0;
				buffer->data[buffer_count++] = '\r';
			}
		}
		buffer->data[buffer_count++] = c;
		count--;
		if (c == '\n' && (mode & POP_DEVICE_MODE_LINE) != 0)
			/* flush output after NL */
			flush = buffer_count;
		else if (c == CTRL_Z && (mode & POP_DEVICE_MODE_TEXT) != 0)
		{
			/* treat as EOF -- flush and terminate output */
			flush = buffer_count;
			device->status |= POP_DEVICE_STATUS_EOF;
			break;
		}
	}

	if ((mode & POP_DEVICE_MODE_CONSOLE) != 0 && buffer_count > buffer_posn)
	{
		/* always flush console output */
		flush = buffer_count;
		pop_set_console_mode(device, TRUE);
	}
	if (flush > buffer_posn)
	{
		status = do_write(device->handle, buffer->data + buffer_posn,
			flush - buffer_posn, &nwritten);
		buffer_posn += nwritten;
		if (status != 0)
		{
			device->status |= status;
			if (status == POP_DEVICE_STATUS_ERROR)
				device->error_code = GetLastError();
		}
	}
	if (buffer_posn == buffer_count)
		buffer->posn = buffer->count = 0;
	else
	{
		buffer->posn = buffer_posn;
		buffer->count = buffer_count;
	}
	/* return number of characters copied out */
	return nbytes - count;
}

BOOL pop_flush_device(POP_DEVICE *device)
{
	POP_DEVICE_BUFFER *buffer = device->out_buffer;
	DWORD nbytes = buffer->count - buffer->posn;
	DWORD status = 0;
	if (nbytes > 0)
	{
		if ((device->mode & POP_DEVICE_MODE_CONSOLE) != 0)
			pop_set_console_mode(device, TRUE);
		status = do_write(device->handle, buffer->data + buffer->posn,
			nbytes, &nbytes);
		if (status == 0)
			buffer->posn = buffer->count = 0;
		else
		{
			buffer->posn += nbytes;
			device->status |= status;
			if (status == POP_DEVICE_STATUS_ERROR)
				device->error_code = GetLastError();
		}
	}

	return status == 0;
}


/***************************************************************************
*                                                                          *
*	Seek on a Device                                                       *
*                                                                          *
***************************************************************************/

DWORD pop_seek_device(POP_DEVICE *device, LONG nbytes, DWORD method)
	/*	Move the file pointer for DEVICE by NBYTES according to METHOD:
			0 = relative to file start (absolute position)
			1 = relative to current position
			2 = relative to file end
	*/
{
	if (device->out_buffer != NULL)
		/* flush any buffered output */
		pop_flush_device(device);
	if (device->in_buffer != NULL)
	{
		/* account for any buffered input */
		if (method == 1)
			nbytes -= device->in_buffer->count - device->in_buffer->posn;
		device->in_buffer->count = device->in_buffer->posn = 0;
	}
	method = method == 0 ? FILE_BEGIN : method == 1 ? FILE_CURRENT : FILE_END;
	nbytes = SetFilePointer(device->handle, nbytes, NULL, method);
	if (nbytes == 0xffffffff)
	{
		device->status |= POP_DEVICE_STATUS_ERROR;
		device->error_code = GetLastError();
	}

	return nbytes;
}


/***************************************************************************
*                                                                          *
*	Close a Device                                                         *
*                                                                          *
***************************************************************************/

void pop_close_device(POP_DEVICE *device)
	/*	Close the handle associated with a device and free its buffers
	*/
{
	device->status = POP_DEVICE_STATUS_EOF;
	CloseHandle(device->handle);
	device->handle = INVALID_HANDLE_VALUE;
	if (device->in_buffer != NULL)
	{
		FREE_BUFFER(device->in_buffer);
		device->in_buffer = NULL;
	}
	if (device->out_buffer != NULL)
	{
		FREE_BUFFER(device->out_buffer);
		device->out_buffer = NULL;
	}
}
