#include "t-utils.h"

const char *cyan = "", *red = "", *green = "", *yellow = "", *reset = "";
unsigned int passed, failed, t_verbose = 0;
static unsigned int t_depth;
static const char *indent_str = "  ";

/* can't be used when a or b has side-effects, but we don't care here */
#define max(a, b) (a > b ? a : b)
#define min(a, b) (a < b ? a : b)
#define delta(a, b) ((max(a, b) - (min(a, b))))


void t_reset(void)
{
	passed = failed = 0;
}

void t_set_colors(int force)
{
	if (force == 1 || isatty(fileno(stdout))) {
		cyan = CLR_CYAN;
		red = CLR_RED;
		yellow = CLR_YELLOW;
		green = CLR_GREEN;
		reset = CLR_RESET;
	}
}

static void t_indent(unsigned int depth)
{
	unsigned int i;
	for (i = 0; i < depth; i++) {
		printf("%s", indent_str);
	}
}

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

	t_indent(t_depth++);
	va_start(ap, fmt);
	printf("%s### ", cyan);
	vfprintf(stdout, fmt, ap);
	printf("%s\n", reset);
	va_end(ap);
}

int t_end(void)
{
	if (t_depth)
		t_depth--;
	if (!t_depth || failed) {
		t_indent(t_depth);
		printf("Test results: %s%u passed%s, %s%u failed%s\n",
			   green, passed, reset, failed ? red : "", failed, failed ? reset : "");
	}

	return failed ? EXIT_FAILURE : EXIT_SUCCESS;
}

static int t_okv(int success, const char *fmt, va_list ap)
{
	passed += !!success;
	failed += !success;

	if (fmt && (!success || t_verbose || (t_verbose = !!getenv("TEST_VERBOSE")))) {
		t_indent(t_depth);
		if (success) {
			printf("%sPASS%s ", green, reset);
		} else {
			printf("%sFAIL%s ", red, reset);
		}
		vfprintf(stdout, fmt, ap);
		putchar('\n');
	}

	return success;
}

int t_ok(int success, const char *fmt, ...)
{
	va_list ap;

	if (fmt) {
		va_start(ap, fmt);
		t_okv(success, fmt, ap);
		va_end(ap);
	}
	else
		t_okv(success, NULL, ap);

	return success;
}

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

	va_start(ap, fmt);
	t_okv(TEST_PASS, fmt, ap);
	va_end(ap);
}

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

	va_start(ap, fmt);
	t_okv(TEST_FAIL, fmt, ap);
	va_end(ap);
}

void t_diag(const char *fmt, ...)
{
	if (fmt) {
		va_list ap;
		t_indent(t_depth + 1);
		va_start(ap, fmt);
		vfprintf(stdout, fmt, ap);
		va_end(ap);
		putchar('\n');
	}
}

int ok_int(int a, int b, const char *name)
{
	if (a == b) {
		t_pass("%s", name);
		return TEST_PASS;
	}

	t_fail("%s", name);
	t_diag("%d != %d. delta: %d", a, b, delta(a, b));
	return TEST_FAIL;
}

int ok_uint(unsigned int a, unsigned int b, const char *name)
{
	if (a == b) {
		t_pass("%s", name);
		return TEST_PASS;
	}

	t_fail("%s", name);
	t_diag("%u != %u. delta: %u", a, b, delta(a, b));
	return TEST_FAIL;
}

int ok_str(const char *a, const char *b, const char *name)
{
	if ((!a && !b) || (a && b && !strcmp(a, b))) {
		t_pass("%s", name);
		return TEST_PASS;
	}

	t_fail("%s", name);
	t_diag("'%s' != '%s'", a, b);
	return TEST_FAIL;
}

void __attribute__((__noreturn__)) crash(const char *fmt, ...)
{
	va_list ap;

	va_start(ap, fmt);
	vfprintf(stderr, fmt, ap);
	va_end(ap);
	fputc('\n', stderr);

	exit(1);
}
