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

/* required for sandboxing */
#include <sys/types.h>
#include <sys/stat.h>

#ifdef _WIN32
#	include <windows.h>
#	include <io.h>
#	include <shellapi.h>
#	include <direct.h>

#	define _MAIN_CC __cdecl

#	define stat(path, st) _stat(path, st)
#	define mkdir(path, mode) _mkdir(path)
#	define chdir(path) _chdir(path)
#	define access(path, mode) _access(path, mode)
#	define strdup(str) _strdup(str)

#	ifndef __MINGW32__
#		pragma comment(lib, "shell32")
#		define strncpy(to, from, to_size) strncpy_s(to, to_size, from, _TRUNCATE)
#		define W_OK 02
#		define S_ISDIR(x) ((x & _S_IFDIR) != 0)
#		define mktemp_s(path, len) _mktemp_s(path, len)
#	endif
	typedef struct _stat STAT_T;
#else
#	include <sys/wait.h> /* waitpid(2) */
#	include <unistd.h>
#	define _MAIN_CC
	typedef struct stat STAT_T;
#endif

#include "clay.h"

static void fs_rm(const char *_source);
static void fs_copy(const char *_source, const char *dest);

static const char *
fixture_path(const char *base, const char *fixture_name);

struct clay_error {
	const char *test;
	int test_number;
	const char *suite;
	const char *file;
	int line_number;
	const char *error_msg;
	char *description;

	struct clay_error *next;
};

static struct {
	const char *active_test;
	const char *active_suite;

	int suite_errors;
	int total_errors;

	int test_count;

	struct clay_error *errors;
	struct clay_error *last_error;

	void (*local_cleanup)(void *);
	void *local_cleanup_payload;

	jmp_buf trampoline;
	int trampoline_enabled;
} _clay;

struct clay_func {
	const char *name;
	void (*ptr)(void);
};

struct clay_suite {
	const char *name;
	struct clay_func initialize;
	struct clay_func cleanup;
	const struct clay_func *tests;
	size_t test_count;
};

/* From clay_print_*.c */
static void clay_print_init(int test_count, int suite_count, const char *suite_names);
static void clay_print_shutdown(int test_count, int suite_count, int error_count);
static void clay_print_error(int num, const struct clay_error *error);
static void clay_print_ontest(const char *test_name, int test_number, int failed);
static void clay_print_onsuite(const char *suite_name);
static void clay_print_onabort(const char *msg, ...);

/* From clay_sandbox.c */
static void clay_unsandbox(void);
static int clay_sandbox(void);

/* Event callback overrides */
#define clay_on_test() /* nop */
#define clay_on_suite() /* nop */

/* Autogenerated test data by clay */
static const struct clay_func _clay_cb_buf_basic[] = {
    {"printf", &test_buf_basic__printf},
	{"resize", &test_buf_basic__resize}
};
static const struct clay_func _clay_cb_config_add[] = {
    {"to_existing_section", &test_config_add__to_existing_section},
	{"to_new_section", &test_config_add__to_new_section}
};
static const struct clay_func _clay_cb_config_new[] = {
    {"write_new_config", &test_config_new__write_new_config}
};
static const struct clay_func _clay_cb_config_read[] = {
    {"blank_lines", &test_config_read__blank_lines},
	{"case_sensitive", &test_config_read__case_sensitive},
	{"empty_files", &test_config_read__empty_files},
	{"header_in_last_line", &test_config_read__header_in_last_line},
	{"invalid_ext_headers", &test_config_read__invalid_ext_headers},
	{"lone_variable", &test_config_read__lone_variable},
	{"multiline_value", &test_config_read__multiline_value},
	{"number_suffixes", &test_config_read__number_suffixes},
	{"prefixes", &test_config_read__prefixes},
	{"simple_read", &test_config_read__simple_read},
	{"subsection_header", &test_config_read__subsection_header}
};
static const struct clay_func _clay_cb_config_stress[] = {
    {"dont_break_on_invalid_input", &test_config_stress__dont_break_on_invalid_input}
};
static const struct clay_func _clay_cb_config_write[] = {
    {"delete_inexistent", &test_config_write__delete_inexistent},
	{"delete_value", &test_config_write__delete_value},
	{"replace_value", &test_config_write__replace_value}
};
static const struct clay_func _clay_cb_core_buffer[] = {
    {"0", &test_core_buffer__0},
	{"1", &test_core_buffer__1},
	{"2", &test_core_buffer__2},
	{"3", &test_core_buffer__3},
	{"4", &test_core_buffer__4},
	{"5", &test_core_buffer__5},
	{"6", &test_core_buffer__6},
	{"7", &test_core_buffer__7},
	{"8", &test_core_buffer__8},
	{"9", &test_core_buffer__9}
};
static const struct clay_func _clay_cb_core_dirent[] = {
    {"dont_traverse_dot", &test_core_dirent__dont_traverse_dot},
	{"dont_traverse_empty_folders", &test_core_dirent__dont_traverse_empty_folders},
	{"traverse_slash_terminated_folder", &test_core_dirent__traverse_slash_terminated_folder},
	{"traverse_subfolder", &test_core_dirent__traverse_subfolder},
	{"traverse_weird_filenames", &test_core_dirent__traverse_weird_filenames}
};
static const struct clay_func _clay_cb_core_filebuf[] = {
    {"0", &test_core_filebuf__0},
	{"1", &test_core_filebuf__1},
	{"2", &test_core_filebuf__2},
	{"3", &test_core_filebuf__3},
	{"4", &test_core_filebuf__4},
	{"5", &test_core_filebuf__5}
};
static const struct clay_func _clay_cb_core_oid[] = {
    {"streq", &test_core_oid__streq}
};
static const struct clay_func _clay_cb_core_path[] = {
    {"0_dirname", &test_core_path__0_dirname},
	{"1_basename", &test_core_path__1_basename},
	{"2_topdir", &test_core_path__2_topdir},
	{"5_joins", &test_core_path__5_joins},
	{"6_long_joins", &test_core_path__6_long_joins},
	{"7_path_to_dir", &test_core_path__7_path_to_dir},
	{"8_self_join", &test_core_path__8_self_join}
};
static const struct clay_func _clay_cb_core_rmdir[] = {
    {"delete_recursive", &test_core_rmdir__delete_recursive},
	{"fail_to_delete_non_empty_dir", &test_core_rmdir__fail_to_delete_non_empty_dir}
};
static const struct clay_func _clay_cb_core_string[] = {
    {"0", &test_core_string__0},
	{"1", &test_core_string__1}
};
static const struct clay_func _clay_cb_core_strtol[] = {
    {"int32", &test_core_strtol__int32},
	{"int64", &test_core_strtol__int64}
};
static const struct clay_func _clay_cb_core_vector[] = {
    {"0", &test_core_vector__0},
	{"1", &test_core_vector__1},
	{"2", &test_core_vector__2}
};
static const struct clay_func _clay_cb_index_rename[] = {
    {"single_file", &test_index_rename__single_file}
};
static const struct clay_func _clay_cb_network_remotelocal[] = {
    {"retrieve_advertised_references", &test_network_remotelocal__retrieve_advertised_references}
};
static const struct clay_func _clay_cb_network_remotes[] = {
    {"fnmatch", &test_network_remotes__fnmatch},
	{"parsing", &test_network_remotes__parsing},
	{"refspec_parsing", &test_network_remotes__refspec_parsing},
	{"transform", &test_network_remotes__transform}
};
static const struct clay_func _clay_cb_object_commit_commitstagedfile[] = {
    {"generate_predictable_object_ids", &test_object_commit_commitstagedfile__generate_predictable_object_ids}
};
static const struct clay_func _clay_cb_object_raw_chars[] = {
    {"build_valid_oid_from_raw_bytes", &test_object_raw_chars__build_valid_oid_from_raw_bytes},
	{"find_invalid_chars_in_oid", &test_object_raw_chars__find_invalid_chars_in_oid}
};
static const struct clay_func _clay_cb_object_raw_compare[] = {
    {"compare_allocfmt_oids", &test_object_raw_compare__compare_allocfmt_oids},
	{"compare_fmt_oids", &test_object_raw_compare__compare_fmt_oids},
	{"compare_pathfmt_oids", &test_object_raw_compare__compare_pathfmt_oids},
	{"succeed_on_copy_oid", &test_object_raw_compare__succeed_on_copy_oid},
	{"succeed_on_oid_comparison_equal", &test_object_raw_compare__succeed_on_oid_comparison_equal},
	{"succeed_on_oid_comparison_greater", &test_object_raw_compare__succeed_on_oid_comparison_greater},
	{"succeed_on_oid_comparison_lesser", &test_object_raw_compare__succeed_on_oid_comparison_lesser}
};
static const struct clay_func _clay_cb_object_raw_convert[] = {
    {"succeed_on_oid_to_string_conversion", &test_object_raw_convert__succeed_on_oid_to_string_conversion},
	{"succeed_on_oid_to_string_conversion_big", &test_object_raw_convert__succeed_on_oid_to_string_conversion_big}
};
static const struct clay_func _clay_cb_object_raw_fromstr[] = {
    {"fail_on_invalid_oid_string", &test_object_raw_fromstr__fail_on_invalid_oid_string},
	{"succeed_on_valid_oid_string", &test_object_raw_fromstr__succeed_on_valid_oid_string}
};
static const struct clay_func _clay_cb_object_raw_hash[] = {
    {"hash_buffer_in_single_call", &test_object_raw_hash__hash_buffer_in_single_call},
	{"hash_by_blocks", &test_object_raw_hash__hash_by_blocks},
	{"hash_commit_object", &test_object_raw_hash__hash_commit_object},
	{"hash_junk_data", &test_object_raw_hash__hash_junk_data},
	{"hash_multi_byte_object", &test_object_raw_hash__hash_multi_byte_object},
	{"hash_one_byte_object", &test_object_raw_hash__hash_one_byte_object},
	{"hash_tag_object", &test_object_raw_hash__hash_tag_object},
	{"hash_tree_object", &test_object_raw_hash__hash_tree_object},
	{"hash_two_byte_object", &test_object_raw_hash__hash_two_byte_object},
	{"hash_vector", &test_object_raw_hash__hash_vector},
	{"hash_zero_length_object", &test_object_raw_hash__hash_zero_length_object}
};
static const struct clay_func _clay_cb_object_raw_short[] = {
    {"oid_shortener_no_duplicates", &test_object_raw_short__oid_shortener_no_duplicates},
	{"oid_shortener_stresstest_git_oid_shorten", &test_object_raw_short__oid_shortener_stresstest_git_oid_shorten}
};
static const struct clay_func _clay_cb_object_raw_size[] = {
    {"validate_oid_size", &test_object_raw_size__validate_oid_size}
};
static const struct clay_func _clay_cb_object_raw_type2string[] = {
    {"check_type_is_loose", &test_object_raw_type2string__check_type_is_loose},
	{"convert_string_to_type", &test_object_raw_type2string__convert_string_to_type},
	{"convert_type_to_string", &test_object_raw_type2string__convert_type_to_string}
};
static const struct clay_func _clay_cb_object_tree_diff[] = {
    {"addition", &test_object_tree_diff__addition},
	{"deletion", &test_object_tree_diff__deletion},
	{"modification", &test_object_tree_diff__modification},
	{"more", &test_object_tree_diff__more}
};
static const struct clay_func _clay_cb_object_tree_frompath[] = {
    {"fail_when_processing_an_invalid_path", &test_object_tree_frompath__fail_when_processing_an_invalid_path},
	{"fail_when_processing_an_unknown_tree_segment", &test_object_tree_frompath__fail_when_processing_an_unknown_tree_segment},
	{"retrieve_tree_from_path_to_treeentry", &test_object_tree_frompath__retrieve_tree_from_path_to_treeentry}
};
static const struct clay_func _clay_cb_odb_loose[] = {
    {"exists", &test_odb_loose__exists},
	{"simple_reads", &test_odb_loose__simple_reads}
};
static const struct clay_func _clay_cb_odb_packed[] = {
    {"mass_read", &test_odb_packed__mass_read},
	{"read_header_0", &test_odb_packed__read_header_0},
	{"read_header_1", &test_odb_packed__read_header_1}
};
static const struct clay_func _clay_cb_odb_sorting[] = {
    {"alternate_backends_sorting", &test_odb_sorting__alternate_backends_sorting},
	{"basic_backends_sorting", &test_odb_sorting__basic_backends_sorting}
};
static const struct clay_func _clay_cb_refs_crashes[] = {
    {"double_free", &test_refs_crashes__double_free}
};
static const struct clay_func _clay_cb_repo_getters[] = {
    {"empty", &test_repo_getters__empty},
	{"head_detached", &test_repo_getters__head_detached},
	{"head_orphan", &test_repo_getters__head_orphan}
};
static const struct clay_func _clay_cb_repo_init[] = {
    {"bare_repo", &test_repo_init__bare_repo},
	{"bare_repo_noslash", &test_repo_init__bare_repo_noslash},
	{"standard_repo", &test_repo_init__standard_repo},
	{"standard_repo_noslash", &test_repo_init__standard_repo_noslash}
};
static const struct clay_func _clay_cb_repo_open[] = {
    {"bare_empty_repo", &test_repo_open__bare_empty_repo},
	{"standard_empty_repo", &test_repo_open__standard_empty_repo}
};
static const struct clay_func _clay_cb_status_single[] = {
    {"hash_single_file", &test_status_single__hash_single_file}
};
static const struct clay_func _clay_cb_status_worktree[] = {
    {"empty_repository", &test_status_worktree__empty_repository},
	{"whole_repository", &test_status_worktree__whole_repository}
};

static const struct clay_suite _clay_suites[] = {
    {
        "buf::basic",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_buf_basic, 2
    },
	{
        "config::add",
        {"initialize", &test_config_add__initialize},
        {"cleanup", &test_config_add__cleanup},
        _clay_cb_config_add, 2
    },
	{
        "config::new",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_config_new, 1
    },
	{
        "config::read",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_config_read, 11
    },
	{
        "config::stress",
        {"initialize", &test_config_stress__initialize},
        {"cleanup", &test_config_stress__cleanup},
        _clay_cb_config_stress, 1
    },
	{
        "config::write",
        {"initialize", &test_config_write__initialize},
        {"cleanup", &test_config_write__cleanup},
        _clay_cb_config_write, 3
    },
	{
        "core::buffer",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_buffer, 10
    },
	{
        "core::dirent",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_dirent, 5
    },
	{
        "core::filebuf",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_filebuf, 6
    },
	{
        "core::oid",
        {"initialize", &test_core_oid__initialize},
        {NULL, NULL},
        _clay_cb_core_oid, 1
    },
	{
        "core::path",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_path, 7
    },
	{
        "core::rmdir",
        {"initialize", &test_core_rmdir__initialize},
        {NULL, NULL},
        _clay_cb_core_rmdir, 2
    },
	{
        "core::string",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_string, 2
    },
	{
        "core::strtol",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_strtol, 2
    },
	{
        "core::vector",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_core_vector, 3
    },
	{
        "index::rename",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_index_rename, 1
    },
	{
        "network::remotelocal",
        {"initialize", &test_network_remotelocal__initialize},
        {"cleanup", &test_network_remotelocal__cleanup},
        _clay_cb_network_remotelocal, 1
    },
	{
        "network::remotes",
        {"initialize", &test_network_remotes__initialize},
        {"cleanup", &test_network_remotes__cleanup},
        _clay_cb_network_remotes, 4
    },
	{
        "object::commit::commitstagedfile",
        {"initialize", &test_object_commit_commitstagedfile__initialize},
        {"cleanup", &test_object_commit_commitstagedfile__cleanup},
        _clay_cb_object_commit_commitstagedfile, 1
    },
	{
        "object::raw::chars",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_chars, 2
    },
	{
        "object::raw::compare",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_compare, 7
    },
	{
        "object::raw::convert",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_convert, 2
    },
	{
        "object::raw::fromstr",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_fromstr, 2
    },
	{
        "object::raw::hash",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_hash, 11
    },
	{
        "object::raw::short",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_short, 2
    },
	{
        "object::raw::size",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_size, 1
    },
	{
        "object::raw::type2string",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_object_raw_type2string, 3
    },
	{
        "object::tree::diff",
        {"initialize", &test_object_tree_diff__initialize},
        {"cleanup", &test_object_tree_diff__cleanup},
        _clay_cb_object_tree_diff, 4
    },
	{
        "object::tree::frompath",
        {"initialize", &test_object_tree_frompath__initialize},
        {"cleanup", &test_object_tree_frompath__cleanup},
        _clay_cb_object_tree_frompath, 3
    },
	{
        "odb::loose",
        {"initialize", &test_odb_loose__initialize},
        {"cleanup", &test_odb_loose__cleanup},
        _clay_cb_odb_loose, 2
    },
	{
        "odb::packed",
        {"initialize", &test_odb_packed__initialize},
        {"cleanup", &test_odb_packed__cleanup},
        _clay_cb_odb_packed, 3
    },
	{
        "odb::sorting",
        {"initialize", &test_odb_sorting__initialize},
        {"cleanup", &test_odb_sorting__cleanup},
        _clay_cb_odb_sorting, 2
    },
	{
        "refs::crashes",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_refs_crashes, 1
    },
	{
        "repo::getters",
        {"initialize", &test_repo_getters__initialize},
        {"cleanup", &test_repo_getters__cleanup},
        _clay_cb_repo_getters, 3
    },
	{
        "repo::init",
        {"initialize", &test_repo_init__initialize},
        {NULL, NULL},
        _clay_cb_repo_init, 4
    },
	{
        "repo::open",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_repo_open, 2
    },
	{
        "status::single",
        {NULL, NULL},
        {NULL, NULL},
        _clay_cb_status_single, 1
    },
	{
        "status::worktree",
        {"initialize", &test_status_worktree__initialize},
        {"cleanup", &test_status_worktree__cleanup},
        _clay_cb_status_worktree, 2
    }
};

static size_t _clay_suite_count = 38;
static size_t _clay_callback_count = 122;

/* Core test functions */
static void
clay_run_test(
	const struct clay_func *test,
	const struct clay_func *initialize,
	const struct clay_func *cleanup)
{
	int error_st = _clay.suite_errors;

	clay_on_test();
	_clay.trampoline_enabled = 1;

	if (setjmp(_clay.trampoline) == 0) {
		if (initialize->ptr != NULL)
			initialize->ptr();

		test->ptr();
	}

	_clay.trampoline_enabled = 0;

	if (_clay.local_cleanup != NULL)
		_clay.local_cleanup(_clay.local_cleanup_payload);

	if (cleanup->ptr != NULL)
		cleanup->ptr();

	_clay.test_count++;

	/* remove any local-set cleanup methods */
	_clay.local_cleanup = NULL;
	_clay.local_cleanup_payload = NULL;

	clay_print_ontest(
		test->name,
		_clay.test_count,
		(_clay.suite_errors > error_st)
	);
}

static void
clay_report_errors(void)
{
	int i = 1;
	struct clay_error *error, *next;

	error = _clay.errors;
	while (error != NULL) {
		next = error->next;
		clay_print_error(i++, error);
		free(error->description);
		free(error);
		error = next;
	}

	_clay.errors = _clay.last_error = NULL;
}

static void
clay_run_suite(const struct clay_suite *suite)
{
	const struct clay_func *test = suite->tests;
	size_t i;

	clay_print_onsuite(suite->name);
	clay_on_suite();

	_clay.active_suite = suite->name;
	_clay.suite_errors = 0;

	for (i = 0; i < suite->test_count; ++i) {
		_clay.active_test = test[i].name;
		clay_run_test(&test[i], &suite->initialize, &suite->cleanup);
	}
}

#if 0 /* temporarily disabled */
static void
clay_run_single(const struct clay_func *test,
	const struct clay_suite *suite)
{
	_clay.suite_errors = 0;
	_clay.active_suite = suite->name;
	_clay.active_test = test->name;

	clay_run_test(test, &suite->initialize, &suite->cleanup);
}
#endif

static void
clay_usage(const char *arg)
{
	printf("Usage: %s [options]\n\n", arg);
	printf("Options:\n");
//	printf("  -tXX\t\tRun only the test number XX\n");
	printf("  -sXX\t\tRun only the suite number XX\n");
	exit(-1);
}

static void
clay_parse_args(int argc, char **argv)
{
	int i;

	for (i = 1; i < argc; ++i) {
		char *argument = argv[i];
		char action;
		int num;

		if (argument[0] != '-')
			clay_usage(argv[0]);

		action = argument[1];
		num = strtol(argument + 2, &argument, 10);

		if (*argument != '\0' || num < 0)
			clay_usage(argv[0]);

		switch (action) {
		case 's':
			if ((size_t)num >= _clay_suite_count) {
				clay_print_onabort("Suite number %d does not exist.\n", num);
				exit(-1);
			}

			clay_run_suite(&_clay_suites[num]);
			break;

		default:
			clay_usage(argv[0]);
		}
	}
}

static int
clay_test(int argc, char **argv)
{
	clay_print_init(
		(int)_clay_callback_count,
		(int)_clay_suite_count,
		""
	);

	if (clay_sandbox() < 0) {
		clay_print_onabort("Failed to sandbox the test runner.\n");
		exit(-1);
	}

	clay_on_init();

	if (argc > 1) {
		clay_parse_args(argc, argv);
	} else {
		size_t i;
		for (i = 0; i < _clay_suite_count; ++i)
			clay_run_suite(&_clay_suites[i]);
	}

	clay_print_shutdown(
		_clay.test_count,
		(int)_clay_suite_count,
		_clay.total_errors
	);

	clay_on_shutdown();

	clay_unsandbox();
	return _clay.total_errors;
}

void
clay__assert(
	int condition,
	const char *file,
	int line,
	const char *error_msg,
	const char *description,
	int should_abort)
{
	struct clay_error *error;

	if (condition)
		return;

	error = calloc(1, sizeof(struct clay_error));

	if (_clay.errors == NULL)
		_clay.errors = error;

	if (_clay.last_error != NULL)
		_clay.last_error->next = error;

	_clay.last_error = error;

	error->test = _clay.active_test;
	error->test_number = _clay.test_count;
	error->suite = _clay.active_suite;
	error->file = file;
	error->line_number = line;
	error->error_msg = error_msg;

	if (description != NULL)
		error->description = strdup(description);

	_clay.suite_errors++;
	_clay.total_errors++;

	if (should_abort) {
		if (!_clay.trampoline_enabled) {
			clay_print_onabort(
				"Fatal error: a cleanup method raised an exception.");
			exit(-1);
		}

		longjmp(_clay.trampoline, -1);
	}
}

void cl_set_cleanup(void (*cleanup)(void *), void *opaque)
{
	_clay.local_cleanup = cleanup;
	_clay.local_cleanup_payload = opaque;
}

static char _clay_path[4096];

static int
is_valid_tmp_path(const char *path)
{
	STAT_T st;

	if (stat(path, &st) != 0)
		return 0;

	if (!S_ISDIR(st.st_mode))
		return 0;

	return (access(path, W_OK) == 0);
}

static int
find_tmp_path(char *buffer, size_t length)
{
#ifndef _WIN32
	static const size_t var_count = 4;
	static const char *env_vars[] = {
		"TMPDIR", "TMP", "TEMP", "USERPROFILE"
 	};

 	size_t i;

	for (i = 0; i < var_count; ++i) {
		const char *env = getenv(env_vars[i]);
		if (!env)
			continue;

		if (is_valid_tmp_path(env)) {
			strncpy(buffer, env, length);
			return 0;
		}
	}

	/* If the environment doesn't say anything, try to use /tmp */
	if (is_valid_tmp_path("/tmp")) {
		strncpy(buffer, "/tmp", length);
		return 0;
	}

#else
	if (GetTempPath((DWORD)length, buffer))
		return 0;
#endif

	/* This system doesn't like us, try to use the current directory */
	if (is_valid_tmp_path(".")) {
		strncpy(buffer, ".", length);
		return 0;
	}

	return -1;
}

static void clay_unsandbox(void)
{
	if (_clay_path[0] == '\0')
		return;

#ifdef _WIN32
	chdir("..");
#endif

	fs_rm(_clay_path);
}

static int build_sandbox_path(void)
{
	const char path_tail[] = "clay_tmp_XXXXXX";
	size_t len;

	if (find_tmp_path(_clay_path, sizeof(_clay_path)) < 0)
		return -1;

	len = strlen(_clay_path);

#ifdef _WIN32
	{ /* normalize path to POSIX forward slashes */
		size_t i;
		for (i = 0; i < len; ++i) {
			if (_clay_path[i] == '\\')
				_clay_path[i] = '/';
		}
	}
#endif

	if (_clay_path[len - 1] != '/') {
		_clay_path[len++] = '/';
	}

	strncpy(_clay_path + len, path_tail, sizeof(_clay_path) - len);

#ifdef _WIN32
	if (mktemp_s(_clay_path, sizeof(_clay_path)) != 0)
		return -1;

	if (mkdir(_clay_path, 0700) != 0)
		return -1;
#else
	if (mkdtemp(_clay_path) == NULL)
		return -1;
#endif

	return 0;
}

static int clay_sandbox(void)
{
	if (_clay_path[0] == '\0' && build_sandbox_path() < 0)
		return -1;

	if (chdir(_clay_path) != 0)
		return -1;

	return 0;
}


static const char *
fixture_path(const char *base, const char *fixture_name)
{
	static char _path[4096];
	size_t root_len;

	root_len = strlen(base);
	strncpy(_path, base, sizeof(_path));

	if (_path[root_len - 1] != '/')
		_path[root_len++] = '/';

	if (fixture_name[0] == '/')
		fixture_name++;

	strncpy(_path + root_len,
		fixture_name,
		sizeof(_path) - root_len);

	return _path;
}

#ifdef CLAY_FIXTURE_PATH
const char *cl_fixture(const char *fixture_name)
{
	return fixture_path(CLAY_FIXTURE_PATH, fixture_name);
}

void cl_fixture_sandbox(const char *fixture_name)
{
	fs_copy(cl_fixture(fixture_name), _clay_path);
}

void cl_fixture_cleanup(const char *fixture_name)
{
	fs_rm(fixture_path(_clay_path, fixture_name));
}
#endif

#ifdef _WIN32

#define FOF_FLAGS (FOF_SILENT | FOF_NOCONFIRMATION | FOF_NOERRORUI | FOF_NOCONFIRMMKDIR)

static char *
fileops_path(const char *_path)
{
	char *path = NULL;
	size_t length, i;

	if (_path == NULL)
		return NULL;

	length = strlen(_path);
	path = malloc(length + 2);

	if (path == NULL)
		return NULL;

	memcpy(path, _path, length);
	path[length] = 0;
	path[length + 1] = 0;

	for (i = 0; i < length; ++i) {
		if (path[i] == '/')
			path[i] = '\\';
	}

	return path;
}

static void
fileops(int mode, const char *_source, const char *_dest)
{
	SHFILEOPSTRUCT fops;

	char *source = fileops_path(_source);
	char *dest = fileops_path(_dest);

	ZeroMemory(&fops, sizeof(SHFILEOPSTRUCT));

	fops.wFunc = mode;
	fops.pFrom = source;
	fops.pTo = dest;
	fops.fFlags = FOF_FLAGS;

	cl_assert_(
		SHFileOperation(&fops) == 0,
		"Windows SHFileOperation failed"
	);

	free(source);
	free(dest);
}

static void
fs_rm(const char *_source)
{
	fileops(FO_DELETE, _source, NULL);
}

static void
fs_copy(const char *_source, const char *_dest)
{
	fileops(FO_COPY, _source, _dest);
}

void
cl_fs_cleanup(void)
{
	fs_rm(fixture_path(_clay_path, "*"));
}

#else
static int
shell_out(char * const argv[])
{
	int status;
	pid_t pid;

	pid = fork();

	if (pid < 0) {
		fprintf(stderr,
			"System error: `fork()` call failed.\n");
		exit(-1);
	}

	if (pid == 0) {
		execv(argv[0], argv);
	}

	waitpid(pid, &status, 0);
	return WEXITSTATUS(status);
}

static void
fs_copy(const char *_source, const char *dest)
{
	char *argv[5];
	char *source;
	size_t source_len;

	source = strdup(_source);
	source_len = strlen(source);

	if (source[source_len - 1] == '/')
		source[source_len - 1] = 0;

	argv[0] = "/bin/cp";
	argv[1] = "-R";
	argv[2] = source;
	argv[3] = (char *)dest;
	argv[4] = NULL;

	cl_must_pass_(
		shell_out(argv),
		"Failed to copy test fixtures to sandbox"
	);

	free(source);
}

static void
fs_rm(const char *source)
{
	char *argv[4];

	argv[0] = "/bin/rm";
	argv[1] = "-Rf";
	argv[2] = (char *)source;
	argv[3] = NULL;

	cl_must_pass_(
		shell_out(argv),
		"Failed to cleanup the sandbox"
	);
}

void
cl_fs_cleanup(void)
{
	clay_unsandbox();
	clay_sandbox();
}
#endif


static void clay_print_init(int test_count, int suite_count, const char *suite_names)
{
	(void)test_count;
	(void)suite_names;
	(void)suite_count;
	printf("TAP version 13\n");
}

static void clay_print_shutdown(int test_count, int suite_count, int error_count)
{
	(void)test_count;
	(void)suite_count;
	(void)error_count;

	if (!error_count)
		printf("# passed all %d test(s)\n", test_count);
	else
		printf("# failed %d among %d test(s)\n", error_count,
			test_count);
	printf("1..%d\n", test_count);
}

static void clay_print_error(int num, const struct clay_error *error)
{
	(void)num;

	printf("  ---\n");
	printf("  message : %s\n", error->error_msg);
	printf("  severity: fail\n");
	printf("  suite   : %s\n", error->suite);
	printf("  test    : %s\n", error->test);
	printf("  file    : %s\n", error->file);
	printf("  line    : %d\n", error->line_number);

	if (error->description != NULL)
		printf("  description: %s\n", error->description);

	printf("  ...\n");
}

static void clay_print_ontest(const char *test_name, int test_number, int failed)
{
	printf("%s %d - %s\n",
		failed ? "not ok" : "ok",
		test_number,
		test_name
	);

	clay_report_errors();
}

static void clay_print_onsuite(const char *suite_name)
{
	printf("# *** %s ***\n", suite_name);
}

static void clay_print_onabort(const char *msg, ...)
{
	va_list argp;
	va_start(argp, msg);
	fprintf(stdout, "Bail out! ");
	vfprintf(stdout, msg, argp);
	va_end(argp);
}


int _MAIN_CC main(int argc, char *argv[])
{
    return clay_test(argc, argv);
}
