#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <limits.h>
#include <stdarg.h>
#include <locale.h>
#include <limits.h>
#include <mba/cfg.h>
#include <mba/csv.h>
#include <mba/msgno.h>

typedef int (*test_fn)(int verbose, struct cfg *cfg, char *args[]);

int PoolExercise(int verbose, struct cfg *cfg, char *args[]);
int PathCanonExamples(int verbose, struct cfg *cfg, char *args[]);
int HashmapExercise(int verbose, struct cfg *cfg, char *args[]);
int HashmapCount(int verbose, struct cfg *cfg, char *args[]);
int HashmapCheck(int verbose, struct cfg *cfg, char *args[]);
int LinkedlistExercise(int verbose, struct cfg *cfg, char *args[]);
int CsvExamples(int verbose, struct cfg *cfg, char *args[]);
int CsvEol(int verbose, struct cfg *cfg, char *args[]);
int StackExercise(int verbose, struct cfg *cfg, char *args[]);
int VarrayOps(int verbose, struct cfg *cfg, char *args[]);
int CfgOps(int verbose, struct cfg *cfg, char *args[]);
int CfgQueryStringExamples(int verbose, struct cfg *cfg, char *args[]);
int CfgSuba(int verbose, struct cfg *cfg, char *args[]);
int TcharAll(int verbose, struct cfg *cfg, char *args[]);
int TextTest(int verbose, struct cfg *cfg, char *args[]);
int DomnodeLoadStore(int verbose, struct cfg *cfg, char *args[]);
int DomnodeOps(int verbose, struct cfg *cfg, char *args[]);
#if !defined(_WIN32)
int ShelloutSimple(int verbose, struct cfg *cfg, char *args[]);
#endif
int EvalExamples(int verbose, struct cfg *cfg, char *args[]);
int SvsemCounter(int verbose, struct cfg *cfg, char *args[]);
int SubaExercise(int verbose, struct cfg *cfg, char *args[]);
int AdtInit(int verbose, struct cfg *cfg, char *args[]);
int SharedAdts(int verbose, struct cfg *cfg, char *args[]);
int BitsetOps(int verbose, struct cfg *cfg, char *args[]);
int SvcondProdCons(int verbose, struct cfg *cfg, char *args[]);
int DiffExamples(int verbose, struct cfg *cfg, char *args[]);
int OpenCre(int verbose, struct cfg *cfg, char *args[]);

struct test {
	int num;
	test_fn test;
} test_tbl[] = {
	{ 100, PoolExercise },
	{ 200, PathCanonExamples },
	{ 300, HashmapExercise },
	{ 302, HashmapCheck },
	{ 400, LinkedlistExercise },
	{ 500, CsvExamples },
	{ 501, CsvExamples },
	{ 502, CsvExamples },
	{ 503, CsvExamples },
	{ 510, CsvEol },
	{ 511, CsvEol },
	{ 512, CsvEol },
	{ 600, StackExercise },
	{ 700, VarrayOps },
	{ 800, CfgOps },
	{ 802, CfgSuba },
	{ 900, TcharAll },
	{ 901, TextTest },
#if !defined(_WIN32)
	{ 301, HashmapCount },
	{ 801, CfgQueryStringExamples },
	{ 1000, ShelloutSimple },
	{ 5100, EvalExamples },
	{ 5400, AdtInit },
	{ 5500, SharedAdts },
	{ 5700, SvcondProdCons },
	{ 5200, SvsemCounter },
	{ 5300, SubaExercise },
	{ 5301, SubaExercise },
	{ 5302, SubaExercise },
	{ 5303, SubaExercise },
	{ 5304, SubaExercise },
	{ 5305, SubaExercise },
	{ 5306, SubaExercise },
	{ 5307, SubaExercise },
	{ 5308, SubaExercise },
	{ 5309, SubaExercise },
	{ 5310, SubaExercise },
	{ 5311, SubaExercise },
	{ 5312, SubaExercise },
	{ 5313, SubaExercise },
	{ 5314, SubaExercise },
	{ 5315, SubaExercise },
	{ 5316, SubaExercise },
	{ 5317, SubaExercise },
	{ 5318, SubaExercise },
	{ 5319, SubaExercise },
	{ 5320, SubaExercise },
#endif
/* DEPRECATED
	{ 1200, MbsSimple },
	{ 1201, MbsSimple },
	{ 1202, MbsSimple },
	{ 1203, MbsSimple },
	{ 1204, MbsSimple },
	{ 1205, MbsSimple },
	{ 1206, MbsSimple },
	{ 1207, MbsSimple },
	{ 1208, MbsSimple },
	{ 1209, MbsSimple },
	{ 1210, MbsSimple },
	{ 1211, MbsSimple },
	{ 1212, MbsSimple },
	{ 1213, MbsSimple },
	{ 1214, MbsSimple },
	{ 1215, MbsSimple },
	{ 1216, MbsSimple },
	{ 1217, MbsSimple },
	{ 1218, MbsSimple },
	{ 5000, DomnodeLoadStore },
	{ 5001, DomnodeOps },
*/
	{ 5600, BitsetOps },
	{ 5800, DiffExamples },
	{ 5900, OpenCre },
	{ 0, NULL }
};

int
run_suite(const char *cfgname, const char *csvname, int testnums[], int tlen)
{
	struct cfg *cfg;
	FILE *csv;
	unsigned char buf[4096], *row[12];
	int tnum, ti, ret, flags;

	if ((cfg = cfg_new(NULL)) == NULL ||
				cfg_load(cfg, cfgname)) {
		AMSG(": %s", cfgname);
		return -1;
	}
	if ((csv = fopen(csvname, "r")) == NULL) {
		PMNF(errno, ": %s", csvname);
		return -1;
	}

	while ((ret = csv_row_fread(csv, buf, 4096, row, 12, ',', CSV_TRIM | CSV_QUOTES)) > 0) { 
		if ((flags = strtol(row[1], NULL, 10)) == LONG_MIN || flags == LONG_MAX) {
			PMNF(errno, ": Invalid test flags: %s", row[1]);
			return -1;
		}
		if (flags == 0) {
			continue;
		}
		if ((tnum = strtol(row[0], NULL, 10)) == LONG_MIN || tnum == LONG_MAX) {
			PMNF(errno, ": Invalid test number: %s", row[0]);
			return -1;
		}
		if (tlen) {
			for (ti = 0; ti < tlen; ti++) {
				if (tnum == testnums[ti]) {
					break;
				}
			}
			if (ti == tlen) {
				continue;
			}
		}

		for (ti = 0; test_tbl[ti].test; ti++) {
			if (tnum == test_tbl[ti].num) {
				break;
			}
		}
		if (test_tbl[ti].test == NULL) {
			PMSG("Test not found: %d", tnum);
			return -1;
		}

		errno = 0;

		fprintf(stderr, "%d:%s: ", tnum, row[2]);
		fflush(stderr);
		if (test_tbl[ti].test(flags > 1 ? flags : 0, cfg, (char **)row + 4) == 0) {
			fprintf(stderr, "pass\n");
		} else {
			MSG("failure");
		}
	}

	fclose(csv);
	cfg_del(cfg);

	return ret;
}

int
main(int argc, char *argv[])
{
	char *arg, *cfgname, *csvname;
	int ai, testnums[256], ti, num;

	setlocale(LC_CTYPE, "");

	cfgname = csvname = NULL;
	for (ai = 1; ai < argc; ai++) {
		arg = argv[ai];
		if (strcmp("-f", arg) == 0) {
			arg = argv[++ai];
			if (ai >= argc) {
				PMNF(EINVAL, ": -f requires a .cfg filename");
				goto err;
			}
			cfgname = arg;
		} else if (strcmp("-t", arg) == 0) {
			arg = argv[++ai];
			if (ai >= argc) {
				PMNF(EINVAL, ": -t requires a .csv filename");
				goto err;
			}
			csvname = arg;
		} else {
			break;
		}
	}
	if (cfgname == NULL) {
		cfgname = "tmba.cfg";
	}
	if (csvname == NULL) {
		csvname = "tmba.csv";
	}

	ti = 0;
	while (ai < argc) {
		arg = argv[ai++];
		if ((num = strtol(arg, NULL, 10)) == LONG_MIN || num == LONG_MAX) {
			PMNF(errno, ": Invalid test number: %s", arg);
			goto err;
		}
		testnums[ti++] = num;
	}

	if (run_suite(cfgname, csvname, testnums, ti) == -1) {
		AMSG("");
		goto err;
	}

	return EXIT_SUCCESS;
err:
	MSG("usage: %s [-f <suite.cfg>] [-t <suite.csv>] [<test num> <test num> <test num> ...]\n", argv[0]);
	return EXIT_FAILURE;
}

