// 19 may 2019
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "testing.h"
#include "testingpriv.h"

void testingprivInternalError(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	fprintf(stderr, "** testing internal error: ");
	vfprintf(stderr, fmt, ap);
	fprintf(stderr, "; aborting\n");
	va_end(ap);
	abort();
}

#define sharedbitsPrefix testingpriv
#include "../../sharedbits/alloc_impl.h"
#include "../../sharedbits/array_impl.h"
#undef sharedbitsPrefix

#define sharedbitsPrefix testingprivImpl
#define sharedbitsStatic static
#define sharedbitsInternalError testingprivInternalError
#include "../../sharedbits/strsafe_impl.h"
#undef sharedbitsInternalError
#undef sharedbitsStatic
#undef sharedbitsPrefix

#define sharedbitsPrefix testingpriv
#define testingprivStrncpy testingprivImplStrncpy
#include "../../sharedbits/strdup_impl.h"
#undef testingprivStrncpy
#undef sharedbitsPrefix

int testingprivVsnprintf(char *s, size_t n, const char *fmt, va_list ap)
{
	int ret;

	ret = testingprivImplVsnprintf(s, n, fmt, ap);
	if (ret < 0)
		testingprivInternalError("encoding error in vsnprintf(); this likely means your call to testingTLogf() and the like is invalid");
	return ret;
}

int testingprivSnprintf(char *s, size_t n, const char *fmt, ...)
{
	va_list ap;
	int ret;

	va_start(ap, fmt);
	ret = testingprivVsnprintf(s, n, fmt, ap);
	va_end(ap);
	return ret;
}

char *testingprivVsmprintf(const char *fmt, va_list ap)
{
	char *s;
	va_list ap2;
	int n;

	va_copy(ap2, ap);
	n = testingprivVsnprintf(NULL, 0, fmt, ap2);
	va_end(ap2);
	s = (char *) testingprivAlloc((n + 1) * sizeof (char), "char[]");
	testingprivVsnprintf(s, n + 1, fmt, ap);
	return s;
}

char *testingprivSmprintf(const char *fmt, ...)
{
	char *s;
	va_list ap;

	va_start(ap, fmt);
	s = testingprivVsmprintf(fmt, ap);
	va_end(ap);
	return s;
}

struct testingprivOutbuf {
	testingprivArray buf;
};

testingprivOutbuf *testingprivNewOutbuf(void)
{
	testingprivOutbuf *o;

	o = testingprivNew(testingprivOutbuf);
	testingprivArrayInit(o->buf, char, 32, "testing output buffer");
	return o;
}

void testingprivOutbufFree(testingprivOutbuf *o)
{
	testingprivArrayFree(o->buf);
	testingprivFree(o);
}

void testingprivOutbufVprintf(testingprivOutbuf *o, const char *fmt, va_list ap)
{
	char *dest;
	va_list ap2;
	int n;

	if (o == NULL) {
		vprintf(fmt, ap);
		return;
	}
	va_copy(ap2, ap);
	n = testingprivVsnprintf(NULL, 0, fmt, ap2);
	va_end(ap2);
	// To conserve memory, we only allocate the terminating NUL once.
	if (o->buf.len == 0)
		dest = (char *) testingprivArrayAppend(&(o->buf), n + 1);
	else {
		dest = (char *) testingprivArrayAppend(&(o->buf), n);
		dest--;
	}
	testingprivVsnprintf(dest, n + 1, fmt, ap);
}

void testingprivOutbufVprintfIndented(testingprivOutbuf *o, const char *fmt, va_list ap)
{
	char *buf;
	char *lineStart, *lineEnd;
	const char *indent;

	buf = testingprivVsmprintf(fmt, ap);

	lineStart = buf;
	indent = "";
	for (;;) {
		lineEnd = strchr(lineStart, '\n');
		if (lineEnd == NULL)
			break;
		*lineEnd = '\0';
		testingprivOutbufPrintf(o, "%s%s\n", indent, lineStart);
		lineStart = lineEnd + 1;
		indent = "    ";
	}
	// and print the last line fragment, if any
	if (*lineStart != '\0')
		testingprivOutbufPrintf(o, "%s%s", indent, lineStart);
	testingprivFree(buf);
}

void testingprivOutbufPrintf(testingprivOutbuf *o, const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	testingprivOutbufVprintf(o, fmt, ap);
	va_end(ap);
}

// TODO right now this assumes the last character in o before calling this is a newline
void testingprivOutbufAppendOutbuf(testingprivOutbuf *o, testingprivOutbuf *src)
{
	char *buf;
	size_t n;
	bool hasTrailingBlankLine;
	size_t trailingBlankLinePos = 0;		// silence incorrect MSVC warning
	char *lineStart, *lineEnd;

	buf = src->buf.buf;
	n = src->buf.len;
	if (n == 0)
		// nothing to write
		return;

	// strip trailing blank lines, if any
	hasTrailingBlankLine = false;
	if (buf[n - 1] == '\n') {
		hasTrailingBlankLine = true;
		while (n > 0 && buf[n - 1] == '\n')
			n--;
		if (n == 0) {
			// the buffer only has blank lines, so just add a single newline and be done with it
			// TODO verify that this is the correct behavior
			testingprivOutbufPrintf(o, "\n");
			return;
		}
		trailingBlankLinePos = n;
		buf[trailingBlankLinePos] = '\0';
	}

	lineStart = buf;
	for (;;) {
		lineEnd = strchr(lineStart, '\n');
		if (lineEnd == NULL)			// last line
			break;
		*lineEnd = '\0';
		testingprivOutbufPrintf(o, "    %s\n", lineStart);
		// be sure to restore src to its original state
		*lineEnd = '\n';
		lineStart = lineEnd + 1;
	}
	// print the last line, if any
	if (*lineStart != '\0')
		testingprivOutbufPrintf(o, "    %s\n", lineStart);

	// restore src to its original state
	if (hasTrailingBlankLine)
		buf[trailingBlankLinePos] = '\n';
}

const char *testingprivOutbufString(testingprivOutbuf *o)
{
	if (o->buf.buf == NULL)
		return "";
	return o->buf.buf;
}