/* ``The contents of this file are subject to the Erlang Public License,
 * Version 1.1, (the "License"); you may not use this file except in
 * compliance with the License. You should have received a copy of the
 * Erlang Public License along with this software. If not, it can be
 * retrieved via the world wide web at http://www.erlang.org/.
 * 
 * Software distributed under the License is distributed on an "AS IS"
 * basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See
 * the License for the specific language governing rights and limitations
 * under the License.
 * 
 * The Initial Developer of the Original Code is Ericsson Utvecklings AB.
 * Portions created by Ericsson are Copyright 1999, Ericsson Utvecklings
 * AB. All Rights Reserved.''
 * 
 *     $Id$
 */

#include "testcase_driver.h"
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
#include <setjmp.h>
#include <string.h>

#ifdef __WIN32__
#undef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF 1
#define vsnprintf _vsnprintf
#endif

#ifndef HAVE_VSNPRINTF
#define HAVE_VSNPRINTF 0
#endif

#define COMMENT_BUF_SZ 4096

#define TESTCASE_FAILED		0
#define TESTCASE_SKIPPED	1
#define TESTCASE_SUCCEEDED	2

typedef struct {
    TestCaseState_t visible;
    ErlDrvPort port;
    int result;
    jmp_buf done_jmp_buf;
    char *comment;
    char comment_buf[COMMENT_BUF_SZ];
} InternalTestCaseState_t;

ErlDrvData testcase_drv_start(ErlDrvPort port, char *command);
void testcase_drv_stop(ErlDrvData drv_data);
void testcase_drv_run(ErlDrvData drv_data, char *buf, int len);

static ErlDrvEntry testcase_drv_entry = { 
    NULL,
    testcase_drv_start,
    testcase_drv_stop,
    testcase_drv_run
};


DRIVER_INIT(testcase_drv)
{
    testcase_drv_entry.driver_name = testcase_name();
    return &testcase_drv_entry;
}

ErlDrvData
testcase_drv_start(ErlDrvPort port, char *command)
{
    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *)
	driver_alloc(sizeof(InternalTestCaseState_t));
    if (!itcs) {
	return ERL_DRV_ERROR_GENERAL;
    }

    itcs->visible.testcase_name = testcase_name();
    itcs->visible.extra = NULL;
    itcs->port = port;
    itcs->result = TESTCASE_FAILED;
    itcs->comment = "";

    return (ErlDrvData) itcs;
}

void
testcase_drv_stop(ErlDrvData drv_data)
{
    testcase_cleanup((TestCaseState_t *) drv_data);
    driver_free((void *) drv_data);
}

void
testcase_drv_run(ErlDrvData drv_data, char *buf, int len)
{
    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) drv_data;
    ErlDrvTermData result_atom;
    ErlDrvTermData msg[12];

    itcs->visible.command = buf;
    itcs->visible.command_len = len;

    if (setjmp(itcs->done_jmp_buf) == 0) {
	testcase_run((TestCaseState_t *) itcs);
	itcs->result = TESTCASE_SUCCEEDED;
    }

    switch (itcs->result) {
    case TESTCASE_SUCCEEDED:
	result_atom = driver_mk_atom("succeeded");
	break;
    case TESTCASE_SKIPPED:
	result_atom = driver_mk_atom("skipped");
	break;
    case TESTCASE_FAILED:
    default:
	result_atom = driver_mk_atom("failed");
	break;
    }

    msg[0] = ERL_DRV_ATOM;
    msg[1] = (ErlDrvTermData) result_atom;

    msg[2] = ERL_DRV_PORT;
    msg[3] = driver_mk_port(itcs->port);

    msg[4] = ERL_DRV_ATOM;
    msg[5] = driver_mk_atom(itcs->visible.testcase_name);
 
    msg[6] = ERL_DRV_STRING;
    msg[7] = (ErlDrvTermData) itcs->comment;
    msg[8] = (ErlDrvTermData) strlen(itcs->comment);

    msg[9] = ERL_DRV_TUPLE;
    msg[10] = (ErlDrvTermData) 4;

    driver_output_term(itcs->port, msg, 11);
}

int
testcase_assertion_failed(TestCaseState_t *tcs,
			  char *file, int line, char *assertion)
{
    testcase_failed(tcs, "%s:%d: Assertion failed: \"%s\"",
		    file, line, assertion);
    return 0;
}

void
testcase_printf(TestCaseState_t *tcs, char *frmt, ...)
{
    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs;
    ErlDrvTermData msg[12];
    va_list va;
    va_start(va, frmt);
#if HAVE_VSNPRINTF
    vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va);
#else
    vsprintf(itcs->comment_buf, frmt, va);
#endif
    va_end(va);

    msg[0] = ERL_DRV_ATOM;
    msg[1] = (ErlDrvTermData) driver_mk_atom("print");

    msg[2] = ERL_DRV_PORT;
    msg[3] = driver_mk_port(itcs->port);

    msg[4] = ERL_DRV_ATOM;
    msg[5] = driver_mk_atom(itcs->visible.testcase_name);

    msg[6] = ERL_DRV_STRING;
    msg[7] = (ErlDrvTermData) itcs->comment_buf;
    msg[8] = (ErlDrvTermData) strlen(itcs->comment_buf);

    msg[9] = ERL_DRV_TUPLE;
    msg[10] = (ErlDrvTermData) 4;

    driver_output_term(itcs->port, msg, 11);
}


void testcase_succeeded(TestCaseState_t *tcs, char *frmt, ...)
{
    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs;
    va_list va;
    va_start(va, frmt);
#if HAVE_VSNPRINTF
    vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va);
#else
    vsprintf(itcs->comment_buf, frmt, va);
#endif
    va_end(va);

    itcs->result = TESTCASE_SUCCEEDED;
    itcs->comment = itcs->comment_buf;

    longjmp(itcs->done_jmp_buf, 1);
}

void testcase_skipped(TestCaseState_t *tcs, char *frmt, ...)
{
    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs;
    va_list va;
    va_start(va, frmt);
#if HAVE_VSNPRINTF
    vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va);
#else
    vsprintf(itcs->comment_buf, frmt, va);
#endif
    va_end(va);

    itcs->result = TESTCASE_SKIPPED;
    itcs->comment = itcs->comment_buf;

    longjmp(itcs->done_jmp_buf, 1);
}

void testcase_failed(TestCaseState_t *tcs, char *frmt, ...)
{
    InternalTestCaseState_t *itcs = (InternalTestCaseState_t *) tcs;
    char buf[10];
    size_t bufsz = sizeof(buf);
    va_list va;
    va_start(va, frmt);
#if HAVE_VSNPRINTF
    vsnprintf(itcs->comment_buf, COMMENT_BUF_SZ, frmt, va);
#else
    vsprintf(itcs->comment_buf, frmt, va);
#endif
    va_end(va);

    itcs->result = TESTCASE_FAILED;
    itcs->comment = itcs->comment_buf;

    if (erl_drv_getenv("ERL_ABORT_ON_FAILURE", buf, &bufsz) == 0
	&& strcmp("true", buf) == 0) {
	fprintf(stderr, "Testcase \"%s\" failed: %s\n",
		itcs->visible.testcase_name, itcs->comment);
	abort();
    }

    longjmp(itcs->done_jmp_buf, 1);
}

void *testcase_alloc(size_t size)
{
    return driver_alloc(size);
}

void *testcase_realloc(void *ptr, size_t size)
{
    return driver_realloc(ptr, size);
}

void testcase_free(void *ptr)
{
    driver_free(ptr);
}
