/*
 * This code is based on CuTest by Asim Jalis
 * http://sourceforge.net/projects/cutest/
 *
 * The license for the original code is
 * LICENSE
 *
 * Copyright (c) 2003 Asim Jalis
 *
 * This software is provided 'as-is', without any express or implied
 * warranty. In no event will the authors be held liable for any damages
 * arising from the use of this software.
 *
 * Permission is granted to anyone to use this software for any purpose,
 * including commercial applications, and to alter it and redistribute it
 * freely, subject to the following restrictions:
 *
 * 1. The origin of this software must not be misrepresented; you must not
 * claim that you wrote the original software. If you use this software in
 * a product, an acknowledgment in the product documentation would be
 * appreciated but is not required.
 *
 * 2. Altered source versions must be plainly marked as such, and must not be
 * misrepresented as being the original software.
 *
 * 3. This notice may not be removed or altered from any source distribution.
 */

#include <config.h>

#include <assert.h>
#include <setjmp.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <math.h>

#include "cutest.h"


#define CU_ALLOC(TYPE)		((TYPE*) malloc(sizeof(TYPE)))
#define HUGE_STRING_LEN	8192
#define STRING_MAX		256

#define asprintf_or_die(strp, fmt, args ...)                            \
    if (asprintf(strp, fmt, ## args) == -1) {                           \
        fprintf(stderr, "Fatal error (probably out of memory)\n");      \
        abort();                                                        \
    }

/*-------------------------------------------------------------------------*
 * CuTest
 *-------------------------------------------------------------------------*/

void CuTestInit(CuTest* t, const char* name, TestFunction function) {
	t->name = strdup(name);
	t->failed = 0;
	t->ran = 0;
	t->message = NULL;
	t->function = function;
	t->jumpBuf = NULL;
}

CuTest* CuTestNew(const char* name, TestFunction function) {
	CuTest* tc = CU_ALLOC(CuTest);
	CuTestInit(tc, name, function);
	return tc;
}

void CuTestRun(CuTest* tc, TestFunction setup, TestFunction teardown) {
	jmp_buf buf;
	tc->jumpBuf = &buf;
	if (setjmp(buf) == 0) {
        if (setup)
            (setup)(tc);
		tc->ran = 1;
		(tc->function)(tc);
	}
    if (teardown && setjmp(buf) == 0) {
        (teardown)(tc);
    }
	tc->jumpBuf = 0;
}

static void CuFailInternal(CuTest* tc, const char* file, int line,
                           const char *string) {
	char *buf = NULL;

	asprintf_or_die(&buf, "%s:%d: %s", file, line, string);

	tc->failed = 1;
	tc->message = buf;
	if (tc->jumpBuf != 0) longjmp(*(tc->jumpBuf), 0);
}

void CuFail_Line(CuTest* tc, const char* file, int line,
                 const char* message2, const char* message) {
    char *string = NULL;

	if (message2 != NULL) {
		asprintf_or_die(&string, "%s:%s", message2, message);
	} else {
        string = strdup(message);
    }
	CuFailInternal(tc, file, line, string);
}

void CuAssert_Line(CuTest* tc, const char* file, int line,
                   const char* message, int condition) {
	if (condition) return;
	CuFail_Line(tc, file, line, NULL, message);
}

void CuAssertStrEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               const char* expected, const char* actual) {
	char *string = NULL;

	if ((expected == NULL && actual == NULL) ||
	    (expected != NULL && actual != NULL &&
	     strcmp(expected, actual) == 0))
        {
            return;
        }

	if (message != NULL) {
        asprintf_or_die(&string, "%s: expected <%s> but was <%s>", message,
                        expected, actual);
	} else {
        asprintf_or_die(&string, "expected <%s> but was <%s>", expected, actual);
    }
	CuFailInternal(tc, file, line, string);
}

void CuAssertIntEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               int expected, int actual) {
	char buf[STRING_MAX];
	if (expected == actual) return;
	sprintf(buf, "expected <%d> but was <%d>", expected, actual);
	CuFail_Line(tc, file, line, message, buf);
}

void CuAssertDblEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               double expected, double actual, double delta) {
	char buf[STRING_MAX];
	if (fabs(expected - actual) <= delta) return;
	sprintf(buf, "expected <%lf> but was <%lf>", expected, actual);
	CuFail_Line(tc, file, line, message, buf);
}

void CuAssertPtrEquals_LineMsg(CuTest* tc, const char* file, int line,
                               const char* message,
                               const void* expected, const void* actual) {
	char buf[STRING_MAX];
	if (expected == actual) return;
	sprintf(buf, "expected pointer <0x%p> but was <0x%p>", expected, actual);
	CuFail_Line(tc, file, line, message, buf);
}

void CuAssertPtrNotEqual_LineMsg(CuTest* tc, const char* file, int line,
                                 const char* message,
                                 const void* expected, const void* actual) {
	char buf[STRING_MAX];
	if (expected != actual) return;
	sprintf(buf, "expected pointer <0x%p> to be different from <0x%p>",
            expected, actual);
	CuFail_Line(tc, file, line, message, buf);
}


/*-------------------------------------------------------------------------*
 * CuSuite
 *-------------------------------------------------------------------------*/

void CuSuiteInit(CuSuite* testSuite) {
	testSuite->count = 0;
	testSuite->failCount = 0;
}

CuSuite* CuSuiteNew(void) {
	CuSuite* testSuite = CU_ALLOC(CuSuite);
	CuSuiteInit(testSuite);
	return testSuite;
}

void CuSuiteSetup(CuSuite *testSuite,
                  TestFunction setup, TestFunction teardown) {
    testSuite->setup = setup;
    testSuite->teardown = teardown;
}

void CuSuiteAdd(CuSuite* testSuite, CuTest *testCase) {
	assert(testSuite->count < MAX_TEST_CASES);
	testSuite->list[testSuite->count] = testCase;
	testSuite->count++;
}

void CuSuiteAddSuite(CuSuite* testSuite, CuSuite* testSuite2) {
	int i;
	for (i = 0 ; i < testSuite2->count ; ++i)
        {
            CuTest* testCase = testSuite2->list[i];
            CuSuiteAdd(testSuite, testCase);
        }
}

void CuSuiteRun(CuSuite* testSuite) {
	int i;
	for (i = 0 ; i < testSuite->count ; ++i)
        {
            CuTest* testCase = testSuite->list[i];
            CuTestRun(testCase, testSuite->setup, testSuite->teardown);
            if (testCase->failed) { testSuite->failCount += 1; }
        }
}

static void string_append(char **s, const char *p) {
    if (*s == NULL) {
        *s = strdup(p);
    } else {
        int len = strlen(*s) + strlen(p) + 1;
        *s = realloc(*s, len);
        strcat(*s, p);
    }
}

void CuSuiteSummary(CuSuite* testSuite, char **summary) {
	int i;

	for (i = 0 ; i < testSuite->count ; ++i)
        {
            CuTest* testCase = testSuite->list[i];
            string_append(summary, testCase->failed ? "F" : ".");
        }
	string_append(summary, "\n\n");
}

void CuSuiteDetails(CuSuite* testSuite, char **details) {
	int i;
	int failCount = 0;
    char *s = NULL;

	if (testSuite->failCount == 0)
        {
            int passCount = testSuite->count - testSuite->failCount;
            const char* testWord = passCount == 1 ? "test" : "tests";
            asprintf_or_die(&s, "OK (%d %s)\n", passCount, testWord);
            string_append(details, s);
            free(s);
        } else {
		if (testSuite->failCount == 1)
			string_append(details, "There was 1 failure:\n");
		else {
            asprintf_or_die(&s, "There were %d failures:\n",
                            testSuite->failCount);
            string_append(details, s);
            free(s);
        }
		for (i = 0 ; i < testSuite->count ; ++i) {
			CuTest* testCase = testSuite->list[i];
			if (testCase->failed) {
				failCount++;
				asprintf_or_die(&s, "%d) %s:\n%s\n",
                         failCount, testCase->name, testCase->message);
                string_append(details, s);
                free(s);
			}
		}
		string_append(details, "\n!!!FAILURES!!!\n");

		asprintf_or_die(&s, "Runs: %d ",   testSuite->count);
        string_append(details, s);
        free(s);

        asprintf_or_die(&s, "Passes: %d ",
                        testSuite->count - testSuite->failCount);
        string_append(details, s);
        free(s);

		asprintf_or_die(&s, "Fails: %d\n",  testSuite->failCount);
        string_append(details, s);
        free(s);
	}
}

/*
 * Local variables:
 *  indent-tabs-mode: nil
 *  c-indent-level: 4
 *  c-basic-offset: 4
 *  tab-width: 4
 * End:
 */
