#include "config.h"

#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <libgen.h>
#include <sys/stat.h>


#ifdef TEST
#include "unit-test.h"
#include "tests-lib.h"
#include <stdbool.h>
#include <errno.h>
#endif


const int EXIT_ERROR = 1;
const int EXIT_USAGE = 2;

static int
usage(FILE *stream) {
	const char *const msg = "Usage:  remembering -p PROFILE -c COMMAND\n";
	if (fprintf(stream, msg) < 0) {
		perror("usage() - fprintf()");
		return -1;
	}
	return 0;
}

static int
help(FILE *stream) {
	static const char *const msg =
		"Options:\n"
		"  -p, PROFILE    profile to be used for gathering and storing data\n"
		"  -c, COMMAND    commant to be run, reading from STDIN, writing to STDOUT\n"
		"  -h, --help     show this help\n"
		"  -V, --version  print the version number\n"
		"\nSee \"man remembering\" for more information\n";
	if (fprintf(stream, msg) < 0) {
		perror("help() - fprint()");
		return -1;
	}
	return 0;
}

static int
version(FILE *stream) {
	const char *const msg = "remembering-" VERSION " " DATE "\n";
	if (fprintf(stream, msg) < 0) {
		perror("version() - fprintf()");
		return -1;
	}
	return 0;
}

static int
missing(FILE *stream, const char *const argument) {
	const char *const msg = "Missing option: %s\n";
	if (fprintf(stream, msg, argument) < 0) {
		perror("missing() - fprintf()");
		return -1;
	}
	return 0;
}

static char *
get_profile_file(const char *const profile, FILE *stream) {
	char *home = getenv("XDG_DATA_HOME");
	if (!home || (strcmp(home, "") == 0)) {
		if (!(home = getenv("HOME"))) {
			fprintf(stream, "Unable to get $XDG_DATA_HOME or $HOME environment variables\n");
			return NULL;
		}
		const char *const suffix = "/.local/share/remembering";
		const size_t home_suffix_size =
			strlen(home)   +
			strlen(suffix) +
			sizeof(char);
		char *const home_with_suffix = malloc(home_suffix_size);
		if (!home_with_suffix) {
			perror("get_profile_file() - malloc() - home_with_suffix");
			return NULL;
		}
		strcpy(home_with_suffix, home);
		strcat(home_with_suffix, suffix);
		home = home_with_suffix;
	} else {
		if (!(home = strdup(home))) {
			perror("get_profile_file() - strdup()");
			return NULL;
		}
	}

	size_t size =
		strlen(home)    +
		strlen("/")     +
		strlen(profile) +
		sizeof(char);
	char *const profile_file = malloc(size);
	if (!profile_file) {
		perror("get_profile_file() - malloc() - profile_file");
		free(home);
		return NULL;
	}
	strcpy(profile_file, home);
	strcat(profile_file, "/");
	strcat(profile_file, profile);

	free(home);
	return profile_file;
}

#ifdef TEST
static void
test_get_profile_file(void) {
	#define EXPECTED_FMT "\nexpected: %s\ngot:      %s\n"
	FILE *f = fopen("/dev/null", "w");
	assert(f);

	{
		testing("get_profile_file() when $XDG_DATA_HOME is *unset*");
		const char *const previous_xdg_data_home =
			getenv("XDG_DATA_HOME");
		unsetenv("XDG_DATA_HOME");

		const char *const HOME = getenv("HOME");
		const char *const SUFFIX = "/.local/share/remembering/";
		const char *const A_PROFILE = "A-PROFILE-NAME";
		assert(HOME);

		const size_t expected_size =
			strlen(HOME)      +
			strlen(SUFFIX)    +
			strlen(A_PROFILE) +
			sizeof(char);
		char *const expected = malloc(expected_size);
		assert(expected);
		strcpy(expected, HOME);
		strcat(expected, SUFFIX);
		strcat(expected, A_PROFILE);

		char *const profile_file = get_profile_file(A_PROFILE, f);
		assert(profile_file);
		assertf(
			strcmp(profile_file, expected) == 0,
			EXPECTED_FMT,
			expected,
			profile_file
		);

		free(profile_file);
		free(expected);
		if (previous_xdg_data_home) {
			setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
		}
		test_ok();
	}
	{
		testing("get_profile_file() when $XDG_DATA_HOME is *empty*");
		const char *const previous_xdg_data_home =
			getenv("XDG_DATA_HOME");
		setenv("XDG_DATA_HOME", "", true);

		const char *const HOME = getenv("HOME");
		const char *const SUFFIX = "/.local/share/remembering/";
		const char *const A_PROFILE = "ANOTHER-PROFILE-NAME";
		assert(HOME);

		const size_t expected_size =
			strlen(HOME)      +
			strlen(SUFFIX)    +
			strlen(A_PROFILE) +
			sizeof(char);
		char *const expected = malloc(expected_size);
		assert(expected);
		strcpy(expected, HOME);
		strcat(expected, SUFFIX);
		strcat(expected, A_PROFILE);

		char *const profile_file = get_profile_file(A_PROFILE, f);
		assert(profile_file);
		assertf(
			strcmp(profile_file, expected) == 0,
			EXPECTED_FMT,
			expected,
			profile_file
		);

		free(profile_file);
		free(expected);
		if (previous_xdg_data_home) {
			setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
		} else {
			unsetenv("XDG_DATA_HOME");
		}
		test_ok();
	}
	{
		testing("get_profile_file() when $XDG_DATA_HOME is *set*");
		const char *const previous_xdg_data_home =
			getenv("XDG_DATA_HOME");

		const char *const XDG_DATA_HOME = "/a/custom/path";
		const char *const A_PROFILE = "YET-ANOTHER-PROFILE-NAME";
		const char *const EXPECTED =
			"/a/custom/path/YET-ANOTHER-PROFILE-NAME";

		setenv("XDG_DATA_HOME", XDG_DATA_HOME, true);

		char *const profile_file = get_profile_file(A_PROFILE, f);
		assert(profile_file);
		assertf(
			strcmp(profile_file, EXPECTED) == 0,
			EXPECTED_FMT,
			EXPECTED,
			profile_file
		);

		free(profile_file);
		if (previous_xdg_data_home) {
			setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
		} else {
			unsetenv("XDG_DATA_HOME");
		}
		test_ok();
	}
	{
		testing("get_profile_file() when $HOME is *unset*");
		const char *const previous_home = getenv("HOME");
		const char *const previous_xdg_data_home =
			getenv("XDG_DATA_HOME");
		unsetenv("XDG_DATA_HOME");
		unsetenv("HOME");

		assert(get_profile_file("ANY-PROFILE", f) == NULL);

		setenv("HOME", previous_home, true);
		if (previous_xdg_data_home) {
			setenv("XDG_DATA_HOME", previous_xdg_data_home, true);
		}
		test_ok();
	}

	fclose(f);
}
#endif

static int
mkdir_p(const char *const path, mode_t mode) {
	struct stat s;
	int ret;

	if (stat(path, &s) == 0 && S_ISDIR(s.st_mode)) {
		return 0;
	}

	char *const path_dup = strdup(path);
	if (!path_dup) {
		perror("mkdir_p() - strdup()");
		return -1;
	}

	const char *const parent = dirname(path_dup);
	if ((ret = mkdir_p(parent, mode))) {
		free(path_dup);
		return ret;
	}

	free(path_dup);
	return mkdir(path, mode);
}

#ifdef TEST
static void
test_mkdir_p(void) {
	{
		testing("mkdir_p() with an existing directory is a noop");
		struct stat s;
		assert(stat("/tmp", &s) == 0 && S_ISDIR(s.st_mode));
		assert(mkdir_p("/tmp", S_IRWXU | S_IRWXG | S_IRWXO) == 0);
		assert(stat("/tmp", &s) == 0 && S_ISDIR(s.st_mode));
		test_ok();
	}
	{
		testing("mkdir_p() with a new directory creates it");
		struct stat s;
		const char *const SUBDIRECTORY_SUFFIX = "/a/b/c/d/e";
		char template[] = "/tmp/remembering.XXXXXX";
		const char *const prefix = mkdtemp(template);
		assert(prefix);

		assert(stat(prefix, &s) == 0 && S_ISDIR(s.st_mode));

		const size_t subdirectory_size =
			strlen(prefix)              +
			strlen(SUBDIRECTORY_SUFFIX) +
			sizeof(char);
		char *const subdirectory = malloc(subdirectory_size);
		assert(subdirectory);
		strcpy(subdirectory, prefix);
		strcat(subdirectory, SUBDIRECTORY_SUFFIX);

		assert(stat(subdirectory, &s) == -1 && errno == ENOENT);
		assert(mkdir_p(subdirectory, S_IRWXU | S_IRWXG | S_IRWXO) == 0);
		assert(stat(subdirectory, &s) == 0 && S_ISDIR(s.st_mode));

		free(subdirectory);
		test_ok();
	}
}
#endif

#ifdef TEST
static void
unit_tests(void) {
	test_get_profile_file();
	test_mkdir_p();
}
#endif

int
main(int argc, char *argv[]) {
#ifdef TEST
	unit_tests();
	return EXIT_SUCCESS;
#endif

	for (int i = 0; i < argc; i++) {
		if (strcmp("--", argv[i]) == 0) {
			break;
		} else if (strcmp("--help", argv[i]) == 0) {
			if (usage(stdout)) return EXIT_ERROR;
			if (help(stdout)) return EXIT_ERROR;
			return EXIT_SUCCESS;
		} else if (strcmp("--version", argv[i]) == 0) {
			if (version(stdout)) return EXIT_ERROR;
			return EXIT_SUCCESS;
		}
	}

	int option;
	const char *profile = NULL;
	const char *command = NULL;
	while ((option = getopt(argc, argv, "p:c:hV")) != -1) {
		switch (option) {
		case 'p':
			profile = optarg;
			break;
		case 'c':
			command = optarg;
			break;
		case 'h':
			if (usage(stdout)) return EXIT_ERROR;
			if (help(stdout)) return EXIT_ERROR;
			return EXIT_SUCCESS;
		case 'V':
			if (version(stdout)) return EXIT_ERROR;
			return EXIT_SUCCESS;
		default:
			if (usage(stderr)) return EXIT_ERROR;
			return EXIT_USAGE;
		}
	}

	if (!profile) {
		if (missing(stderr, "-p PROFILE")) return EXIT_ERROR;
		if (usage(stderr)) return EXIT_ERROR;
		return EXIT_USAGE;
	}

	if (!command) {
		if (missing(stderr, "-c COMMAND")) return EXIT_ERROR;
		if (usage(stderr)) return EXIT_ERROR;
		return EXIT_USAGE;
	}

	/* End getopt() */


	int ret = EXIT_SUCCESS;

	char *profile_file = NULL;
	char *profile_file_dup = NULL;
	if (!(profile_file = get_profile_file(profile, stderr))) {
		ret = EXIT_ERROR;
		goto cleanup;
	}

	if (!(profile_file_dup = strdup(profile_file))) {
		perror("main() - strdup()");
		ret = EXIT_ERROR;
		goto cleanup;
	}

	if ((ret = mkdir_p(dirname(profile_file_dup),
			S_IRWXU | S_IRWXG | S_IRWXO))) {
		goto cleanup;
	}

	printf("profile: %s\ncommand: %s\n", profile, command);
	printf("profile_file: %s\n", profile_file);


cleanup:
	free(profile_file_dup);
	free(profile_file);
	return ret;
}