#include <stdio.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>

#include "../sysck.h"

#ifdef __APPLE__
#define LS_NOTFOUND_EXIT_CODE 1
#else
#define LS_NOTFOUND_EXIT_CODE 2
#endif

// I never remember how to do this, so I shamelessly lifted it from
// StackOverflow, see
// https://stackoverflow.com/questions/4832603/how-could-i-temporary-redirect-stdout-to-a-file-in-a-c-program

int redirect_stdout_to_null() {
	fflush(stdout);
	int bak = dup(1);
	int new = open("/dev/null", O_WRONLY);
	dup2(new, 1);
	close(new);
	
	return bak;
}

int redirect_stderr_to_null() {
	fflush(stderr);
	int bak = dup(2);
	int new = open("/dev/null", O_WRONLY);
	dup2(new, 2);
	close(new);
	
	return bak;
}

void restore_stout(int bak) {
	fflush(stdout);
	dup2(bak, 1);
	close(bak);
}

void restore_sterr(int bak) {
	fflush(stderr);
	dup2(bak, 2);
	close(bak);
}

/* Execute a test (by passing a command string to system(3)) and print a
 * result message to stdout. Arguments are test name, test command, and expected result.
 * Standard output streams are redirected to /dev/null for the duration of the
 * test. Counts the number of tests and successes, which are available by
 * passing the special values NULL or "" as first argument, otherwise returns 1
 * if the test is successful, 0 otherwise.
 */
int test(const char *case_name, const char *cmd, int expected_result) {
	static int num_tests = 0;
	static int num_successes = 0;

	if (NULL == case_name)
		return num_tests;
	if (0 == strncmp("", case_name, 1))
		return num_successes;

	int stdout_bak, stderr_bak = -1;
	// Better not initialize to 0
	int sys_return = -INT_MAX;

	printf("Checking %s... ", case_name);

	stdout_bak = redirect_stdout_to_null();
	stderr_bak = redirect_stderr_to_null();
	sys_return = system(cmd);
	restore_stout(stdout_bak);
	restore_sterr(stderr_bak);

	int result = -1;
	num_tests++;
	int actual_result = sysck(sys_return);
	if (actual_result == expected_result) {
		printf("ok");
		num_successes++;
		result = 1;
	} else {
		printf("failure - expected %d, got %d",
				expected_result, actual_result);
		result = 0;
	}
	printf(".\n");

	// This is not used for now, but may be useful later.
	return result;
}

int main() {
	printf("Starting test of sysck()\n\n");

	// NOTE: the following are all cases in which system(3) is able to at
	// least create and excute a shell in which the supplied command is then
	// run, and in which the shell exits normally (for example, not via a
	// signal). At this point, the result of system() is really the exit
	// status of the the shell, which is that of the last command it
	// executed, and hence of our test command (exception: command not
	// found, in which we receive 127 as the exit status of the shell). All
	// the above may fail, but they are harder to test, so for now we only
	// test exit status. See man system(3) and man waitpid(2).
	test("successful builtin (exit 0)", "exit 0", 0);
	test("successful external command (ls)", "ls", 0);
	test("failing builtin (exit 1)", "exit 1", 1);
	test("nonexistent command", "nosuch", 127);
	test("file not found", "ls notfound", LS_NOTFOUND_EXIT_CODE);
	test("file not executable", "./noexec", 126);

	int num_tests = test(NULL, "", 0);
	int num_successes = test("", "", 0);
	printf("\n%d tests, %d successes, %d failures.\n",
			num_tests, num_successes, num_tests - num_successes);
}

