#define _POSIX_C_SOURCE 200809L
#include <scfg.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

const char src[] =
	"dir1 param1 param2 \"\" param4\n"
	"dir2\n"
	"dir3 param1 {\n"
	"	child1 param1\n"
	"	child2\n"
	"}\n"
	"dir4 {\n"
	"}\n"
	"\n"
	"# comment\n"
	"dir5 \"param 1\" 'param 2' \"a \\b ' \\\\ \\\" c\" 'd \\ e \" f'\n";

const char formatted[] =
	"dir1 param1 param2 \"\" param4\n"
	"dir2\n"
	"dir3 param1 {\n"
	"	child1 param1\n"
	"	child2\n"
	"}\n"
	"dir4\n"
	"dir5 \"param 1\" \"param 2\" \"a b ' \\\\ \\\" c\" \"d \\\\ e \\\" f\"\n";

static const struct scfg_block ref = {
	.directives_len = 5,
	.directives = (struct scfg_directive[]){
		{
			.name = "dir1",
			.params_len = 4,
			.params = (char *[]){ "param1", "param2", "", "param4" },
		},
		{
			.name = "dir2",
		},
		{
			.name = "dir3",
			.params_len = 1,
			.params = (char *[]){"param1"},
			.children = {
				.directives_len = 2,
				.directives = (struct scfg_directive[]){
					{
						.name = "child1",
						.params_len = 1,
						.params = (char *[]){"param1"},
					},
					{
						.name = "child2",
					},
				},
			},
		},
		{
			.name = "dir4",
		},
		{
			.name = "dir5",
			.params_len = 4,
			.params = (char *[]){"param 1", "param 2", "a b ' \\ \" c", "d \\ e \" f"},
		},
	},
};

static bool block_equals(const struct scfg_block *a, const struct scfg_block *b);

static bool directive_equals(const struct scfg_directive *a,
		const struct scfg_directive *b) {
	if (strcmp(a->name, b->name) != 0) {
		fprintf(stderr, "directive name mismatch: '%s' != '%s'\n",
			a->name, b->name);
		return false;
	}

	if (a->params_len != b->params_len) {
		fprintf(stderr, "directive '%s': params length mismatch: %zu != %zu\n",
			a->name, a->params_len, b->params_len);
		return false;
	}

	for (size_t i = 0; i < a->params_len; i++) {
		const char *a_param = a->params[i];
		const char *b_param = b->params[i];
		if (strcmp(a_param, b_param) != 0) {
			fprintf(stderr, "directive '%s': param %zu mismatch: '%s' != '%s'\n",
				a->name, i, a_param, b_param);
			return false;
		}
	}

	if (!block_equals(&a->children, &b->children)) {
		fprintf(stderr, "  in directive '%s'\n", a->name);
		return false;
	}

	return true;
}

static bool block_equals(const struct scfg_block *a, const struct scfg_block *b) {
	if (a->directives_len != b->directives_len) {
		fprintf(stderr, "block length mismatch: %zu != %zu\n",
			a->directives_len, b->directives_len);
		return false;
	}

	for (size_t i = 0; i < a->directives_len; i++) {
		const struct scfg_directive *a_dir = &a->directives[i];
		const struct scfg_directive *b_dir = &b->directives[i];
		if (!directive_equals(a_dir, b_dir)) {
			return false;
		}
	}

	return true;
}

bool test_parse(void) {
	FILE *f = fmemopen((char *)src, strlen(src), "r");

	struct scfg_block cfg = {0};
	int ret = scfg_parse_file(&cfg, f);
	fclose(f);
	if (ret != 0) {
		fprintf(stderr, "failed to parse file: %s\n", strerror(-ret));
		return false;
	}

	bool ok = block_equals(&cfg, &ref);
	scfg_block_finish(&cfg);
	return ok;
}

bool test_format(void) {
	char *str = NULL;
	size_t str_size = 0;
	FILE *f = open_memstream(&str, &str_size);
	int ret = scfg_format_file(&ref, f);
	fclose(f);
	if (ret != 0) {
		fprintf(stderr, "failed to format file\n");
		return false;
	}

	bool ok = strcmp(str, formatted) == 0;
	free(str);
	return ok;
}

int main(int argc, char *argv[]) {
	if (argc < 2) {
		fprintf(stderr, "usage: scfg_test <name>\n");
		return 1;
	}
	const char *test_case = argv[1];

	bool ok;
	if (strcmp(test_case, "parse") == 0) {
		ok = test_parse();
	} else if (strcmp(test_case, "format") == 0) {
		ok = test_format();
	} else {
		fprintf(stderr, "unknown test case '%s'\n", test_case);
		return 1;
	}

	return ok ? 0 : 1;
}
