// 27 february 2018
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
#include <string.h>
#include "timer.h"
#include "testing.h"
#include "testingpriv.h"

struct defer {
	void (*f)(testingT *, void *);
	void *data;
	struct defer *next;
};

#ifdef _MSC_VER
// Microsoft defines jmp_buf with a __declspec(align()), and for whatever reason, they have a warning that triggers when you use that for any reason, and that warning is enabled with /W4
// Silence the warning; it's harmless.
#pragma warning(push)
#pragma warning(disable: 4324)
#endif

struct testingT {
	// set at test creation time
	const char *name;
	void (*f)(testingT *, void *);
	void *data;

	// for sorting tests in a set; not used by subtests
	const char *file;
	long line;

	// test status
	bool failed;
	bool skipped;
	bool returned;
	jmp_buf returnNowBuf;

	// deferred functions
	struct defer *defers;
	bool defersRun;

	// execution options
	testingOptions opts;

	// output
	testingprivOutbuf *outbuf;
};

#ifdef _MSC_VER
#pragma warning(pop)
#endif

static void initTest(testingT *t, const char *name, void (*f)(testingT *, void *), void *data, const char *file, long line)
{
	memset(t, 0, sizeof (testingT));
	t->name = name;
	t->f = f;
	t->data = data;
	t->file = file;
	t->line = line;
}

struct testingSet {
	testingprivArray tests;
};

static testingSet mainTests = { testingprivArrayStaticInit(testingT, 32, "testingT[]") };

void testingprivSetRegisterTest(testingSet **pset, const char *name, void (*f)(testingT *, void *), void *data, const char *file, long line)
{
	testingSet *set;
	testingT *t;

	set = &mainTests;
	if (pset != NULL) {
		set = *pset;
		if (set == NULL) {
			set = testingprivNew(testingSet);
			testingprivArrayInit(set->tests, testingT, 32, "testingT[]");
			*pset = set;
		}
	}
	t = (testingT *) testingprivArrayAppend(&(set->tests), 1);
	initTest(t, name, f, data, file, line);
}

static int testcmp(const void *a, const void *b)
{
	const testingT *ta = (const testingT *) a;
	const testingT *tb = (const testingT *) b;
	int ret;

	ret = strcmp(ta->file, tb->file);
	if (ret != 0)
		return ret;
	if (ta->line < tb->line)
		return -1;
	if (ta->line > tb->line)
		return 1;
	return 0;
}

static void runDefers(testingT *t)
{
	struct defer *d;

	if (t->defersRun)
		return;
	t->defersRun = true;
	for (d = t->defers; d != NULL; d = d->next)
		(*(d->f))(t, d->data);
	// and now free the defers
	// we could use t->defers == NULL instead of t->defersRun but then recursive calls to runDefers() (for instance, if a deferred function calls testingTFatalf()) would explode
	while (t->defers != NULL) {
		d = t->defers;
		t->defers = t->defers->next;
		testingprivFree(d);
	}
}

static const testingOptions defaultOptions = {
	.Verbose = 0,
};

static bool testingprivTRun(testingT *t, testingprivOutbuf *parentbuf)
{
	const char *status;
	timerTime start, end;
	char timerstr[timerDurationStringLen];
	bool printStatus;

	if (t->opts.Verbose)
		testingprivOutbufPrintf(parentbuf, "=== RUN   %s\n", t->name);
	t->outbuf = testingprivNewOutbuf();

	start = timerMonotonicNow();
	if (setjmp(t->returnNowBuf) == 0)
		(*(t->f))(t, t->data);
	end = timerMonotonicNow();
	t->returned = true;
	runDefers(t);

	printStatus = t->opts.Verbose;
	status = "PASS";
	if (t->failed) {
		status = "FAIL";
		printStatus = true;			// always print status on failure
	} else if (t->skipped)
		// note that failed overrides skipped
		status = "SKIP";
	timerDurationString(timerTimeSub(end, start), timerstr);
	if (printStatus) {
		testingprivOutbufPrintf(parentbuf, "--- %s: %s (%s)\n", status, t->name, timerstr);
		testingprivOutbufAppendOutbuf(parentbuf, t->outbuf);
	}

	testingprivOutbufFree(t->outbuf);
	t->outbuf = NULL;
	return !t->failed;
}

// TODO make options and opts consistent throughout? what about format vs fmt in public-facing functions?
static void testingprivSetRun(testingSet *set, const testingOptions *opts, testingprivOutbuf *outbuf, bool *anyFailed)
{
	size_t i;
	testingT *t;

	testingprivArrayQsort(&(set->tests), testcmp);
	t = (testingT *) (set->tests.buf);
	for (i = 0; i < set->tests.len; i++) {
		t->opts = *opts;
		if (!testingprivTRun(t, outbuf))
			*anyFailed = true;
		t++;
	}
}

void testingSetRun(testingSet *set, const struct testingOptions *options, bool *anyRun, bool *anyFailed)
{
	*anyRun = false;
	*anyFailed = false;
	if (set == NULL)
		set = &mainTests;
	if (options == NULL)
		options = &defaultOptions;
	if (set->tests.len == 0)
		return;
	testingprivSetRun(set, options, NULL, anyFailed);
	*anyRun = true;
}

void testingprivTLogfFullThen(testingT *t, void (*then)(testingT *t), const char *file, long line, const char *format, ...)
{
	va_list ap;

	va_start(ap, format);
	// run va_end() *before* then, just to be safe
	testingprivTLogvfFullThen(t, NULL, file, line, format, ap);
	va_end(ap);
	if (then != NULL)
		(*then)(t);
}

static const char *basename(const char *file)
{
	const char *p;

	for (;;) {
		p = strpbrk(file, "/\\");
		if (p == NULL)
			break;
		file = p + 1;
	}
	return file;
}

void testingprivTLogvfFullThen(testingT *t, void (*then)(testingT *t), const char *file, long line, const char *format, va_list ap)
{
	testingprivOutbufPrintf(t->outbuf, "%s:%ld: ", basename(file), line);
	testingprivOutbufVprintfIndented(t->outbuf, format, ap);
	testingprivOutbufPrintf(t->outbuf, "\n");
	if (then != NULL)
		(*then)(t);
}

void testingTFail(testingT *t)
{
	t->failed = true;
}

static void returnNow(testingT *t)
{
	if (!t->returned) {
		// set this now so a FailNow inside a Defer doesn't longjmp twice
		t->returned = true;
		// run defers before calling longjmp() just to be safe
		runDefers(t);
		longjmp(t->returnNowBuf, 1);
	}
}

void testingTFailNow(testingT *t)
{
	testingTFail(t);
	returnNow(t);
}

void testingTSkipNow(testingT *t)
{
	t->skipped = true;
	returnNow(t);
}

void testingTDefer(testingT *t, void (*f)(testingT *t, void *data), void *data)
{
	struct defer *d;

	d = testingprivNew(struct defer);
	d->f = f;
	d->data = data;
	// add to the head of the list so defers are run in reverse order of how they were added
	d->next = t->defers;
	t->defers = d;
}

void testingTRun(testingT *t, const char *subname, void (*subfunc)(testingT *t, void *data), void *data)
{
	testingT *subt;
	testingprivOutbuf *rewrittenName;
	char *fullName;

	rewrittenName = testingprivNewOutbuf();
	while (*subname != '\0') {
		const char *replaced;

		replaced = NULL;
		switch (*subname) {
		case ' ':
		case '\f':
		case '\n':
		case '\r':
		case '\t':
		case '\v':
			replaced = "_";
			break;
		case '\a':
			replaced = "\\a";
			break;
		case '\b':
			replaced = "\\b";
			break;
		}
		if (replaced != NULL)
			testingprivOutbufPrintf(rewrittenName, "%s", replaced);
		else if (isprint(*subname))
			testingprivOutbufPrintf(rewrittenName, "%c", *subname);
		else
			testingprivOutbufPrintf(rewrittenName, "\\x%x", (unsigned int) (*subname));
		subname++;
	}
	fullName = testingprivSmprintf("%s/%s", t->name, testingprivOutbufString(rewrittenName));
	testingprivOutbufFree(rewrittenName);

	subt = testingprivNew(testingT);
	initTest(subt, fullName, subfunc, data, NULL, 0);
	subt->opts = t->opts;
	if (!testingprivTRun(subt, t->outbuf))
		t->failed = true;
	testingprivFree(subt);

	testingprivFree(fullName);
}

// Utility functions, provided here to avoid mucking up the sharedbits functions.

char *testingUtilStrdup(const char *s)
{
	return testingprivStrdup(s);
}

void testingUtilFreeStrdup(char *s)
{
	testingprivFree(s);
}