/* %Z%%M% %I% %E% */

/*
 * Copyright (c) 1990, 1991, 1993, 1994, 1995, 2004 by Wayne C. Gramlich.
 * All rights reserved.
 */

/* This package implements a general write buffer package: */

#ifndef ERROR_EXPORTS_H
#include "error_exports.h"
#endif

#ifndef HEAP_EXPORTS_H
#include "heap_exports.h"
#endif

#ifndef LIBC_EXPORTS_H
#include "libc_exports.h"
#endif

#ifndef STR_EXPORTS_H
#include "str_exports.h"
#endif	

#ifndef UNIX_STDARG_H
#include "unix_stdarg.h"
#endif

#ifndef UNIX_FCNTL_H
#include "unix_fcntl.h"
#endif

#ifndef UNIX_MEMORY_H
#include "unix_memory.h"
#endif

#ifndef UNIX_STDIO_H
#include "unix_stdio.h"
#endif

#ifndef UNIX_UNISTD_H
#include "unix_unistd.h"
#endif

#ifndef WBUF_DEFS_H
#include "wbuf_defs.h"
#endif

/* LINTLIBRARY */

/*
 * wbuf_buffer(wbuf, buffer, size)
 *	This routine will write "size" bytes of "buffer" into "wbuf".
 */
void
wbuf_buffer(
	Wbuf		wbuf,
	Str		buffer,
	int		size)
{
	int		available;
	int		count;

	while (size > 0) {
		available = wbuf->end - wbuf->pointer;
		count = (size > available) ? available : size;
		(void)memcpy(wbuf->pointer, buffer, count);
		wbuf->pointer += count;
		buffer += count;
		if (wbuf->pointer == wbuf->end) {
			wbuf_flush(wbuf);
		}
		size -= count;
	}
}

/*
 * wbuf_buffer_get(wbuf)
 *	This routine will return the current buffer associated with "wbuf".
 */
Str
wbuf_buffer_get(
	Wbuf		wbuf)
{
	return wbuf->buffer;
}

/*
 * wbuf_buffer_set(wbuf, buffer, size)
 *	This routine will store "buffer" and "size" in "wbuf".  The
 *	data point will point to the same relative location in the new
 *	buffer.
 */
void
wbuf_buffer_set(
	Wbuf		wbuf,
	Str		buffer,
	int		size)
{
	int		offset;

	offset = wbuf->pointer - wbuf->buffer;
	wbuf->buffer = buffer;
	wbuf->end = buffer + size;
	wbuf->pointer = buffer + offset;
	wbuf->size = size;
}

/*
 * wbuf_chr(wbuf, chr)
 *	This routine will insert "chr" into "wbuf".
 */
void
wbuf_chr(
	Wbuf		wbuf,
	char		chr)
{
	*wbuf->pointer++ = chr;
	if (wbuf->pointer == wbuf->end) {
		wbuf_flush(wbuf);
	}
}

/*
 * wbuf_close(wbuf)
 *	This routine will flush and close "wbuf" prior to deallocating it.
 */
void
wbuf_close(
	Wbuf		wbuf)
{
	wbuf_flush(wbuf);
	(*wbuf->close_routine)(wbuf, wbuf->handle);
	(void)heap_free(wbuf->heap, (Pointer)wbuf);
}

/*
 * wbuf_create(buffer, size, close_routine, flush_routine, handle, heap)
 *	This routine will create and return a new Wbuf object alloctaed
 *	from "heap".  A buffer of "size" bytes is bound to the new
 *	wbuf.  The "size"'th byte will be set to '\0'.  If "buffer"
 *	is (Str)0, "size" bytes will be allocated from "heap".
 *	"flush_routine(buffer, size, wbuf, handle)" is called whenenver
 *	the buffer is full, or when the user invokes wbuf_flush().
 *	"close_routine(wbuf, handle)" is invoked whenever the user
 *	invokes wbuf_close().  "handle" is a client data pointer.
 */
Wbuf
wbuf_create(
	Str		buffer,
	int		size,
	Wbuf_close	close_routine,
	Wbuf_flush	flush_routine,
	Pointer		handle,
	Heap		heap)
{
	Wbuf		wbuf;

	wbuf = heap_allocate(heap, Wbuf);
	if (buffer == (Str)0) {
		buffer = (Str)heap_alloc(heap, size);
	}
	wbuf->buffer = buffer;
	wbuf->pointer = buffer;
	wbuf->end = buffer + size;
	wbuf->size = size;
	wbuf->close_routine = close_routine;
	wbuf->flush_routine = flush_routine;
	wbuf->handle = handle;
	wbuf->heap = heap;
	return wbuf;
}

/*
 * get_page_size()
 *	This routine will return the page size.
 */
static
unsigned get_page_size()
{
	static unsigned page_size = 0;

	if (page_size == 0) {
		page_size = 1024 << 3;
	}
	return page_size;
}

/*
 * wbuf_file_create(file_name, abort, heap)
 *	This routine will create and return a Wbuf that writes data into
 *	the file named "file_name".  If "abort" is 1, a fatal error occurs
 *	if "file_name" can not be successfully created; otherwise, (Wbuf)0
 *	is returned.  The Wbuf object is allocated from "heap".
 */
Wbuf
wbuf_file_create(
	Str		file_name,
	int		abort,
	Heap		heap)
{
	int		fd;
	Wbuf_file	file;
	Wbuf		wbuf;

	fd = open(file_name, O_CREAT | O_TRUNC | O_WRONLY, 0664);
	if (fd < 0) {
		if (abort) {
			error_fatal("Could not open %s for writing\n",
				    file_name);
		} else {
			return (Wbuf)0;
		}
	}
	file = heap_allocate(heap, Wbuf_file);
	file->fd = fd;
	file->file_name = strdupl(file_name, heap);
	wbuf = wbuf_create((Str)0, get_page_size(), wbuf_file_close,
			   wbuf_file_flush, (Pointer)file, heap);
	return wbuf;
}

/*
 * wbuf_file_close(wbuf, file)
 *	 This routine will flush and close the "wbuf" containing "file".
 */
void
wbuf_file_close(
	Wbuf		wbuf,
	Wbuf_file	file)
{
	wbuf_flush(wbuf);
	if (close(file->fd) < 0) {
		error_fatal("Could not close %s\n", /* TCOVOK */
			    file->file_name); /* TCOVOK */
	}
	(void)heap_free(wbuf->heap, (Pointer)file);
}

/*
 * wbuf_file_flush(buffer, size, wbuf, file)
 *	This routine will flush the "size" bytes of "buffer" to the "file"
 *	pointed to by "file".
 */
/* ARGSUSED */
void
wbuf_file_flush(
	Str		buffer,
	int		size,
	Wbuf		wbuf,
	Wbuf_file	file)
{
	if (write(file->fd, buffer, size) < 0) {
		error_fatal("Difficulty writing %d bytes to %s\n",
			    size, file->file_name);  /* TCOVOK */
	}
}

/*
 * wbuf_flush(wbuf)
 *	This routine will flush the contents of "wbuf".
 */
void
wbuf_flush(
	Wbuf		wbuf)
{
	int		amount;

	amount = wbuf->pointer - wbuf->buffer;
	if (amount > 0) {
		wbuf->pointer = wbuf->buffer;
		(*wbuf->flush_routine)(wbuf->buffer, amount,
				       wbuf, wbuf->handle);
	}
}

/*
 * wbuf_int(wbuf, number)
 *	This routine will write "number" into "wbuf".
 */
void
wbuf_int(
	Wbuf		wbuf,
	int		number)
{
	wbuf_buffer(wbuf, (Str)&number, sizeof(int));
}

/*
 * wbuf_offset_get(wbuf)
 *	This routine will return the offset of "wbuf".
 */
int
wbuf_offset_get(
	Wbuf		wbuf)
{
	return wbuf->pointer - wbuf->buffer;
}

/*
 * wbuf_offset_set(wbuf, offset)
 *	This routine will return will set "offset" in in "wbuf".
 */
void
wbuf_offset_set(
	Wbuf		wbuf,
	int		offset)
{
	wbuf->pointer = wbuf->buffer + offset;
}

/*
 * wbuf_print(wbuf, format, arg1, ..., argN)
 *	This routine will print "arg1" through "argN" into "wbuf" using
 *	"format".
 */
void
wbuf_print(
	Wbuf		wbuf,
	Str		format,
	...)
{
	va_list		args;

	va_start(args, format);
	wbuf_print_varargs(wbuf, format, args);
	va_end(args);
}

/*
 * wbuf_print_varargs(wbuf, format, args)
 */
void
wbuf_print_varargs(
	Wbuf		wbuf,
	Str		format,
	va_list		args)
{
	va_list		temp_args;
	char		buffer[1000];
	int		size;
	Str		temp;
	Str		ptr;

	/* Estimate the size of the output string. */
	temp_args = args;
	size = strlen(format) + 1;
	for (ptr = strchr(format, '%');
	     ptr != (char *)0; ptr = strchr(ptr, '%')) {
		ptr++;
		if (ptr[0] != '%') {
			ptr += strspn(ptr, "0123456789.l");
			if (ptr[0] == 's') {
				/* String: */
				size += strlen(va_arg(temp_args, char *));
			} else {
				/* Number or character: */
				va_arg(temp_args, int);
				size += 10;
			}
		}
	}

	if (size < sizeof(buffer)) {
		size = vsprintf(buffer, format, args);
		wbuf_buffer(wbuf, buffer, size);
	} else {
		temp = (Str)heap_alloc(wbuf->heap, size + 1);
		size = vsprintf(temp, format, args);
		wbuf_buffer(wbuf, temp, size);
		heap_free(wbuf->heap, (Pointer)temp);
	}
}

/*
 * wbuf_size_get(wbuf)
 *	This routine will return the current buffer size for "wbuf".
 */
int
wbuf_size_get(
	Wbuf		wbuf)
{
	return wbuf->size;
}

/*
 * wbuf_str(wbuf, str)
 *	This routine will write "str" into "wbuf".
 */
void
wbuf_str(
	Wbuf		wbuf,
	Str		str)
{
	wbuf_buffer(wbuf, str, strlen(str));
}

