// Copyright © 2024 Rot127 <unisono@quyllur.org>
// SPDX-License-Identifier: BSD-3

#define _XOPEN_SOURCE 500
#include "../../../utils.h"
#include "test_run.h"
#include <capstone/platform.h>
#include <ftw.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>

// Pointer to the file list table
// Must be a thread local, because we cannot pass arguments to `nftw`.
// So the found test files can only be saved, very annoyingly,
// to a global/thread-local mutable variables.
char ***test_files = NULL;
uint32_t file_count = 0;

static void help(const char *self)
{
	fprintf(stderr, "%s <test-file-dir>/<test-file.yml> ...\n", self);
}

static int handle_ftree_entry(const char *fpath, const struct stat *sb,
			      int typeflag, struct FTW *ftwbuf)
{
	if (typeflag != FTW_F) {
		return 0;
	}
	const char *suffix = strstr(fpath, ".yaml");
	if (!suffix || suffix - fpath != strlen(fpath) - 5) {
		// Misses the .yaml suffix.
		return 0;
	}

	file_count++;
	*test_files = cs_mem_realloc(*test_files, sizeof(char *) * file_count);
	if (!*test_files) {
		fprintf(stderr, "[!] realloc failed\n");
		return -1;
	}
	test_files[0][file_count - 1] = cs_strdup(fpath);
	return 0;
}

/// Parses the test file paths from the @argv array.
static void get_tfiles(int argc, const char **argv)
{
	for (size_t i = 1; i < argc; ++i) {
		if (nftw(argv[i], handle_ftree_entry, 20,
			 FTW_DEPTH | FTW_PHYS) == -1) {
			fprintf(stderr, "[!] nftw failed.\n");
			return;
		}
	}
}

void print_test_run_stats(const TestRunStats *stats)
{
	printf("\n-----------------------------------------\n");
	printf("Test run statistics\n\n");
	printf("Valid files: %" PRId32 "\n", stats->valid_test_files);
	printf("Invalid files: %" PRId32 "\n", stats->invalid_files);
	printf("Errors: %" PRId32 "\n\n", stats->errors);
	printf("Test cases:\n");
	printf("\tTotal: %" PRId32 "\n", stats->tc_total);
	printf("\tSuccessful: %" PRId32 "\n", stats->successful);
	printf("\tSkipped: %" PRId32 "\n", stats->skipped);
	printf("\tFailed: %" PRId32 "\n", stats->failed);
	printf("\n\tDecoded instructions: %" PRId32 "\n", stats->decoded_insns);
	printf("-----------------------------------------\n");
	printf("\n");
}

int main(int argc, const char **argv)
{
	if (argc < 2 || strcmp(argv[1], "-h") == 0 ||
	    strcmp(argv[1], "--help") == 0) {
		help(argv[0]);
		exit(EXIT_FAILURE);
	}
	test_files = malloc(sizeof(char **));
	*test_files = NULL;

	get_tfiles(argc, argv);
	if (!*test_files || file_count == 0) {
		fprintf(stderr, "Arguments are invalid. No files found.\n");
		exit(EXIT_FAILURE);
	}

	printf("Test files found: %" PRId32 "\n", file_count);
	TestRunStats stats = { 0 };
	TestRunResult res = cstest_run_tests(*test_files, file_count, &stats);

	print_test_run_stats(&stats);
	if (res == TEST_RUN_ERROR) {
		fprintf(stderr, "[!] An error occured.\n");
		exit(EXIT_FAILURE);
	} else if (res == TEST_RUN_SUCCESS) {
		printf("[o] All tests succeeded.\n");
		exit(EXIT_SUCCESS);
	} else if (res == TEST_RUN_FAILURE) {
		printf("\nNOTE: Asserts have the actual data on the left side: 'actual' != 'expected'\n\n");
		fprintf(stderr, "[!] Some tests failed.\n");
		exit(EXIT_FAILURE);
	}

	fprintf(stderr, "[!] Unhandled Test Run result\n");
	exit(EXIT_FAILURE);
}
