#include <errno.h>
#include <scfg.h>
#include <stdbool.h>
#include <string.h>

static bool contains_special_char(const char *str) {
	for (size_t i = 0; str[i] != '\0'; i++) {
		switch (str[i]) {
		case '"':
		case '\\':
		case '\r':
		case '\n':
		case '\'':
		case '{':
		case '}':
		case ' ':
		case '\t':
			return true;
		}
	}
	return false;
}

static bool write_str(const char *str, FILE *f) {
	size_t n = strlen(str);
	return fwrite(str, 1, n, f) == n;
}

static bool write_ch(char ch, FILE *f) {
	return fwrite(&ch, 1, 1, f) == 1;
}

static bool write_indent(int level, FILE *f) {
	for (int i = 0; i < level; i++) {
		if (!write_ch('\t', f)) {
			return false;
		}
	}
	return true;
}

static bool format_word(const char *str, FILE *f) {
	if (str[0] != '\0' && !contains_special_char(str)) {
		return write_str(str, f);
	}

	if (!write_ch('"', f)) {
		return false;
	}

	for (size_t i = 0; str[i] != '\0'; i++) {
		char ch = str[i];
		switch (ch) {
		case '"':
		case '\\':
			if (!write_ch('\\', f)) {
				return false;
			}
			break;
		}
		if (!write_ch(ch, f)) {
			return false;
		}
	}

	return write_ch('"', f);
}

static int format_block(const struct scfg_block *block, FILE *f, int indent);

static int format_directive(const struct scfg_directive *dir, FILE *f, int indent) {
	if (dir->name[0] == '\0') {
		return -EINVAL;
	}

	if (!write_indent(indent, f) || !format_word(dir->name, f)) {
		return -errno;
	}

	for (size_t i = 0; i < dir->params_len; i++) {
		if (!write_ch(' ', f) || !format_word(dir->params[i], f)) {
			return -errno;
		}
	}

	if (dir->children.directives_len > 0) {
		if (!write_str(" {\n", f)) {
			return -errno;
		}
		int ret = format_block(&dir->children, f, indent + 1);
		if (ret < 0) {
			return ret;
		}
		if (!write_indent(indent, f) || !write_str("}", f)) {
			return -errno;
		}
	}

	if (!write_ch('\n', f)) {
		return -errno;
	}

	return 0;
}

static int format_block(const struct scfg_block *block, FILE *f, int indent) {
	for (size_t i = 0; i < block->directives_len; i++) {
		int ret = format_directive(&block->directives[i], f, indent);
		if (ret != 0) {
			return ret;
		}
	}
	return 0;
}

int scfg_format_file(const struct scfg_block *block, FILE *f) {
	return format_block(block, f, 0);
}

int scfg_store_file(const struct scfg_block *block, const char *path) {
	FILE *f = fopen(path, "w");
	if (f == NULL) {
		return -errno;
	}

	int res = scfg_format_file(block, f);
	fclose(f);
	return res;
}
