/*
 * Copyright 2001, 2002, 2003, 2004, 2005, 2013, 2025 Adam Sampson <ats@offog.org>
 *
 * Permission to use, copy, modify, and/or distribute this software for any
 * purpose with or without fee is hereby granted, provided that the above
 * copyright notice and this permission notice appear in all copies.
 *
 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
 */

#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdarg.h>
#include "iolib.h"
#include "config.h"

/* Default file descriptors. */
int fd_in = 0;
int fd_out = 1;
int fd_err = 2;

/* The granularity with which to allocate buffer sizes;
   the allocated size of a buffer will always be a multiple of this. */
#define BUFFERUNIT 256

/* Die with a constant error message. */
#define die(s) do { write(2, s, sizeof(s) - 1); exit(20); } while (0)

/* Adjust the size of a buffer without destroying its contents. */
static void bsetsize(buffer *b, size_t len) {
	if (len == 0) {
		if (b->s != NULL)
			free(b->s);
		b->s = NULL;
		b->size = 0;
	} else {
		/* This always rounds up the allocated size so that
		   adding characters to a buf one at a time doesn't
		   cause a realloc for each character.

		   We need to be careful about overflow here, since
		   this could wrap for a large allocation. */
		size_t size = BUFFERUNIT * ((len / BUFFERUNIT) + 1);
		size_t padded = size + 1;

		if (size < len)
			die("buffer size too large");
		if (padded <= size)
			die("buffer size too large");

		if (size != b->size) {
			char *s = realloc(b->s, padded);
			if (s == NULL)
				die("out of memory");
			b->s = s;
			b->size = size;
		}
	}
}

void bsetlength(buffer *b, size_t len) {
	bsetsize(b, len);
	b->len = len;
}

void bfree(buffer *b) {
	bsetlength(b, 0);
}

void bmake(buffer *b, const char *s) {
	bsetlength(b, 0);
	bappendm(b, s, strlen(s));
}

void bappendm(buffer *dest, const char *src, size_t len) {
	const size_t orig_len = dest->len;
	bsetlength(dest, orig_len + len);
	if (dest->s != NULL)
		memcpy(dest->s + orig_len, src, len);
}

void bappends(buffer *dest, const char *s) {
	bappendm(dest, s, strlen(s));
}

void bappendc(buffer *dest, char c) {
	bappendm(dest, &c, 1);
}

void bappend(buffer *dest, const buffer *src) {
	bappendm(dest, src->s, src->len);
}

size_t blength(const buffer *b) {
	return b->len;
}

const char *bstr(const buffer *b) {
	if (b->len == 0) return "";
	b->s[b->len] = '\0';
	return b->s;
}

ssize_t bindex(const buffer *b, char c) {
	char *pos;

	if (b->len == 0)
		return -1;

	pos = (char *) memchr(b->s, c, b->len);
	if (pos == NULL)
		return -1;
	else
		return pos - b->s;
}

void breplacec(buffer *b, char from, char to) {
	char *p = b->s, *end = p + b->len;
	for (; p < end; p++) {
		if (*p == from) *p = to;
	}
}

void bpopl(buffer *b, size_t n, buffer *saveto) {
	if (saveto != NULL)
		bappendm(saveto, b->s, n);
	/* FIXME: It would be more efficient to maintain a "start" pointer
	   and only memmove once we have more than a certain amount of space
	   wasted. */
	b->len -= n;
	memmove(b->s, b->s + n, b->len);
}

int writea(int fd, const char *s, size_t n) {
	while (n > 0) {
		int c;

		do {
			c = write(fd, s, n);
		} while (c < 0 && errno == EINTR);
		if (c < 0)
			return -1;

		s += c;
		n -= c;
	}
	return 0;
}

ssize_t writeb(int fd, const buffer *b) {
	return write(fd, b->s, b->len);
}

int writeba(int fd, const buffer *b) {
	return writea(fd, b->s, b->len);
}

ssize_t readb(int fd, buffer *b, size_t n) {
	int c;

	if (n == 0)
		return 0;

	if (b->size < (b->len + n))
		bsetsize(b, b->len + n);

	c = read(fd, b->s + b->len, n);
	if (c > 0)
		b->len += c;

	return c;
}

int readba(int fd, buffer *b) {
	ssize_t c;
	do {
		c = readb(fd, b, 4096);
	} while (c > 0);
	return c;
}

int readuntilb(int fd, buffer *b, size_t max, char term, buffer *overflow) {
	while (1) {
		ssize_t c;
		char *pos = NULL;

		if (overflow->s != NULL)
			pos = (char *) memchr(overflow->s, term, overflow->len);
		if (pos != NULL) {
			/* Remove the \n from overflow, but also trim it
			   from the string read. */
			bpopl(overflow, 1 + pos - overflow->s, b);
			bsetlength(b, b->len - 1);
			return 1;
		}

		/* If no maximum size is specified, then read as much as
		   possible. */
		c = readb(fd, overflow, (max) ? (max - overflow->len) : 4096);
		if (c <= 0)
			return c;
	}
}

int readlineb(int fd, buffer *b, size_t max, buffer *overflow) {
	return readuntilb(fd, b, max, '\n', overflow);
}

/* Return the representation of a digit. */
static char format_digit(int n) {
	if (n < 10) return '0' + n;
	if (n < 36) return 'A' + (n - 10);
	return '!';
}

/* Format an unsigned long into the buffer. */
static void format_ulong(buffer *b, unsigned long n, int base) {
	if (n >= base) format_ulong(b, n / base, base);
	bappendc(b, format_digit(n % base));
}

/* Format a signed long into the buffer. */
static void format_long(buffer *b, long n, int base) {
	if (n < 0) {
		bappendc(b, '-');
		format_ulong(b, -n, base);
	} else {
		format_ulong(b, n, base);
	}
}

/* Format a list of arguments into the buffer. */
static void vaformat(buffer *b, const char *fmt, va_list va) {
	while (1) {
		const char *p = strchr(fmt, '@');
		if (p == NULL)
			break;

		bappendm(b, fmt, p - fmt);
		++p;

		switch (*p) {
		case 'b': {
			buffer *src = va_arg(va, buffer *);
			bappend(b, src);
			break;
		}
		case 'c': {
			char *s = va_arg(va, char *);
			bappends(b, s);
			break;
		}
		case 'i': {
			int i = va_arg(va, int);
			format_long(b, i, 10);
			break;
		}
		case 'I': {
			unsigned int i = va_arg(va, unsigned int);
			format_ulong(b, i, 10);
			break;
		}
		case 'x': {
			int i = va_arg(va, int);
			format_long(b, i, 16);
			break;
		}
		case 'X': {
			unsigned int i = va_arg(va, unsigned int);
			format_ulong(b, i, 16);
			break;
		}
		case 'l': {
			long i = va_arg(va, long);
			format_long(b, i, 10);
			break;
		}
		case 'L': {
			unsigned long i = va_arg(va, unsigned long);
			format_ulong(b, i, 10);
			break;
		}
		case 'a': {
			unsigned long i = va_arg(va, unsigned long);
			format_long(b, i & 0xff, 10);
			bappendc(b, '.');
			format_long(b, (i >> 8) & 0xff, 10);
			bappendc(b, '.');
			format_long(b, (i >> 16) & 0xff, 10);
			bappendc(b, '.');
			format_long(b, (i >> 24) & 0xff, 10);
			break;
		}
		case '@': {
			bappendc(b, '@');
			break;
		}
		case '\0':
			die("unexpected end of format");
		default: 
			die("unknown format character");
		}

		fmt = p + 1;
	}
	bappends(b, fmt);
}

int format(int fd, const char *fmt, ...) {
	int r;
	va_list va;
	buffer b = BUFFER;

	va_start(va, fmt);
	vaformat(&b, fmt, va);
	r = writeba(fd, &b);
	bfree(&b);
	va_end(va);

	return r;
}

void bformat(buffer *b, const char *fmt, ...) {
	va_list va;

	va_start(va, fmt);
	vaformat(b, fmt, va);
	va_end(va);
}

