#include <getopt.h>
#include <stdarg.h>
#include <stdlib.h>
#include <string.h>

#include "def.h"
#include "util.h"

#ifdef __GNUC__
#define ATTRIB_PRINTF(start, end) __attribute__((format(printf, start, end)))
#else
#define ATTRIB_PRINTF(start, end)
#endif

static struct varlink_interface *iface = NULL;
static char *prefix = NULL;

ATTRIB_PRINTF(1, 2)
static char *format_str(const char *fmt, ...) {
	va_list args;
	va_start(args, fmt);
	int len = vsnprintf(NULL, 0, fmt, args);
	va_end(args);
	if (len < 0) {
		return NULL;
	}

	size_t size = (size_t)len + 1;
	char *str = malloc(size);
	if (str == NULL) {
		return NULL;
	}

	va_start(args, fmt);
	vsnprintf(str, size, fmt, args);
	va_end(args);

	return str;
}

static void print_indent(FILE *f, int level) {
	for (int i = 0; i < level; i++) {
		fprintf(f, "	");
	}
}

ATTRIB_PRINTF(3, 4)
static void fprintf_indent(FILE *f, int level, const char *fmt, ...) {
	print_indent(f, level);

	va_list args;
	va_start(args, fmt);
	vfprintf(f, fmt, args);
	va_end(args);
}

static void print_iface_include_guard(FILE *f, const char *iface_name) {
	for (size_t i = 0; iface_name[i] != '\0'; i++) {
		char ch = iface_name[i];
		if ('a' <= ch && ch <= 'z') {
			ch -= 'a' - 'A';
		}
		if (ch == '.' || ch == '-') {
			fprintf(f, "_");
		} else {
			fprintf(f, "%c", ch);
		}
	}
	fprintf(f, "_H");
}

static void print_description(FILE *f, int indent, const char *description) {
	size_t len = strlen(description);
	if (len > 0 && description[len - 1] == '\n') {
		len--;
	}

	fprintf_indent(f, indent, "/**\n");
	fprintf_indent(f, indent, " * ");
	for (size_t i = 0; i < len; i++) {
		char ch = description[i];
		fputc(ch, f);
		if (ch == '\n') {
			fprintf_indent(f, indent, " * ");
		}
	}
	fprintf(f, "\n");
	fprintf_indent(f, indent, " */\n");
}

// See https://en.cppreference.com/w/c/keyword.html
static const char *const reserved_keywords[] = {
	"alignas",
	"alignof",
	"auto",
	"bool",
	"break",
	"case",
	"char",
	"const",
	"constexpr",
	"continue",
	"default",
	"do",
	"double",
	"else",
	"enum",
	"extern",
	"false",
	"float",
	"for",
	"goto",
	"if",
	"inline",
	"int",
	"long",
	"nullptr",
	"register",
	"restrict",
	"return",
	"short",
	"signed",
	"sizeof",
	"static",
	"static_assert",
	"struct",
	"switch",
	"thread_local",
	"true",
	"typedef",
	"typeof",
	"typeof_unqual",
	"union",
	"unsigned",
	"void",
	"volatile",
	"while",
};

#define RESERVED_KEYWORDS_LEN (sizeof(reserved_keywords) / sizeof(reserved_keywords[0]))

#define ESCAPED_KEYWORD_CAP 128
static char escaped_keywords[ESCAPED_KEYWORD_CAP][RESERVED_KEYWORDS_LEN] = {0};

static const char *escape_keyword(const char *str) {
	for (size_t i = 0; i < RESERVED_KEYWORDS_LEN; i++) {
		if (strcmp(str, reserved_keywords[i]) == 0) {
			char *escaped = escaped_keywords[i];
			snprintf(escaped, ESCAPED_KEYWORD_CAP, "%s_", str);
			return escaped;
		}
	}
	return str;
}

static void gen_type(FILE *f, int indent, const char *name, const struct varlink_type *t);

static void gen_struct(FILE *f, int indent, const char *name, const struct varlink_struct *s) {
	fprintf(f, "struct %s_%s {\n", prefix, name);
	for (size_t i = 0; i < s->fields_len; i++) {
		const char *field_name = s->field_names[i];
		const struct varlink_type *t = &s->field_types[i];

		char *nested_name = format_str("%s_%s", name, field_name);

		print_indent(f, indent + 1);
		gen_type(f, indent + 1, nested_name, t);
		fprintf(f, "%s;\n", escape_keyword(field_name));

		free(nested_name);
	}
	if (s->fields_len == 0) {
		print_indent(f, indent + 1);
		fprintf(f, "char _;\n");
	}
	fprintf_indent(f, indent, "}");
}

static void gen_enum(FILE *f, int indent, const char *name, const struct varlink_enum *e) {
	fprintf(f, "enum %s_%s {\n", prefix, name);
	for (size_t i = 0; i < e->entries_len; i++) {
		fprintf_indent(f, indent, "	%s_%s_%s,\n", prefix, name, e->entries[i]);
	}
	fprintf_indent(f, indent, "}");
}

static void gen_type_alias(FILE *f, const struct varlink_type_alias *type_alias) {
	if (type_alias->description != NULL) {
		print_description(f, 0, type_alias->description);
	}

	switch (type_alias->type.kind) {
	case VARLINK_STRUCT:
		gen_struct(f, 0, type_alias->name, &type_alias->type.struct_);
		break;
	case VARLINK_ENUM:
		gen_enum(f, 0, type_alias->name, &type_alias->type.enum_);
		break;
	default:
		abort(); // unreachable
	}
	fprintf(f, ";\n");
}

static const struct varlink_type *find_type(const char *name) {
	for (size_t i = 0; i < iface->type_aliases_len; i++) {
		if (strcmp(iface->type_aliases[i].name, name) == 0) {
			return &iface->type_aliases[i].type;
		}
	}
	abort();
}

static void gen_type(FILE *f, int indent, const char *name, const struct varlink_type *t) {
	char *nested_name;
	switch (t->kind) {
	case VARLINK_STRUCT:
		gen_struct(f, indent, name, &t->struct_);
		fprintf(f, " ");
		break;
	case VARLINK_ENUM:
		gen_enum(f, indent, name, &t->enum_);
		fprintf(f, " ");
		break;
	case VARLINK_NAME:;
		const struct varlink_type *ref_type = find_type(t->name);
		const char *kind;
		switch (ref_type->kind) {
		case VARLINK_STRUCT:
			kind = "struct";
			break;
		case VARLINK_ENUM:
			kind = "enum";
			break;
		default:
			abort();
		}
		fprintf(f, "%s %s_%s ", kind, prefix, t->name);
		break;
	case VARLINK_BOOL:
		fprintf(f, "bool ");
		break;
	case VARLINK_INT:
		fprintf(f, "int ");
		break;
	case VARLINK_FLOAT:
		fprintf(f, "double ");
		break;
	case VARLINK_STRING:
		fprintf(f, "char *");
		break;
	case VARLINK_ARRAY:
		nested_name = format_str("%s_entry", name);

		fprintf(f, "struct %s_%s {\n", prefix, name);
		print_indent(f, indent + 1);
		gen_type(f, indent + 1, nested_name, t->inner);
		fprintf(f, "*data;\n");
		fprintf_indent(f, indent, "	size_t len;\n");
		fprintf_indent(f, indent, "} ");

		free(nested_name);
		break;
	case VARLINK_MAP:
		nested_name = format_str("%s_value", name);

		fprintf(f, "struct %s_%s {\n", prefix, name);
		fprintf_indent(f, indent, "	struct %s_%s_entry {\n", prefix, name);
		fprintf_indent(f, indent, "		char *key;\n");
		print_indent(f, indent + 2);
		gen_type(f, indent + 2, nested_name, t->inner);
		fprintf(f, "value;\n");
		fprintf_indent(f, indent, "	} *data;\n");
		fprintf_indent(f, indent, "	size_t len;\n");
		fprintf_indent(f, indent, "} ");

		free(nested_name);
		break;
	case VARLINK_OBJECT:
		fprintf(f, "struct json_object *");
		break;
	}

	if (t->nullable && t->kind != VARLINK_STRING) {
		fprintf(f, "*");
	}
}

static void gen_type_ref(FILE *f, const char *name, const struct varlink_type *t) {
	switch (t->kind) {
	case VARLINK_STRUCT:
	case VARLINK_ENUM:
	case VARLINK_ARRAY:
	case VARLINK_MAP:
		fprintf(f, "%s %s_%s %s",
			t->kind == VARLINK_ENUM ? "enum" : "struct",
			prefix, name,
			t->nullable ? "*" : "");
		break;
	default:
		gen_type(f, 0, name, t);
		break;
	}
}

static void gen_enum_encoder_code(FILE *f, int indent, const char *name, const char *out, const char *val, const struct varlink_enum *e) {
	fprintf_indent(f, indent, "switch (%s) {\n", val);

	for (size_t i = 0; i < e->entries_len; i++) {
		const char *entry = e->entries[i];

		fprintf_indent(f, indent, "case %s_%s_%s:\n", prefix, name, entry);
		fprintf_indent(f, indent, "	%s = json_object_new_string(\"%s\");\n",
			out, entry);
		fprintf_indent(f, indent, "	break;\n");
	}

	fprintf_indent(f, indent, "}\n");
}

static void gen_struct_encoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s);

static void gen_type_encoder_code(FILE *f, int indent, const char *name, const char *out_expr, const char *in_expr, const struct varlink_type *t) {
	fprintf_indent(f, indent, "struct json_object *%s = ", out_expr);

	if (t->nullable) {
		fprintf(f, "NULL;\n");

		fprintf_indent(f, indent, "if (%s != NULL) {\n", in_expr);
		indent++;

		fprintf_indent(f, indent, "%s = ", out_expr);
	}

	char *val = format_str("%s%s",
		t->nullable && t->kind != VARLINK_STRING ? "*" : "",
		in_expr);

	char *nested_accessor;
	if (t->nullable) {
		nested_accessor = format_str("%s->", in_expr);
	} else {
		nested_accessor = format_str("%s.", in_expr);
	}

	char *nested_name;
	switch (t->kind) {
	case VARLINK_STRUCT:
		fprintf(f, "json_object_new_object();\n");
		if (t->struct_.fields_len == 0) {
			break;
		}
		fprintf_indent(f, indent, "{\n");
		fprintf_indent(f, indent, "	struct json_object *raw = %s;\n", out_expr);

		gen_struct_encoder_code(f, indent + 1, name, nested_accessor, &t->struct_);

		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_ENUM:
		fprintf(f, "NULL;\n");

		gen_enum_encoder_code(f, indent, name, out_expr, val, &t->enum_);
		break;
	case VARLINK_NAME:;
		const struct varlink_type *ref_type = find_type(t->name);
		const char *prefix = "";
		if (t->nullable && ref_type->kind == VARLINK_ENUM) {
			prefix = "*";
		}
		if (!t->nullable && ref_type->kind == VARLINK_STRUCT) {
			prefix = "&";
		}

		fprintf(f, "%s_encode(%s%s);\n",
			t->name, prefix, in_expr);
		break;
	case VARLINK_BOOL:
		fprintf(f, "json_object_new_boolean(%s);\n", val);
		break;
	case VARLINK_INT:
		fprintf(f, "json_object_new_int(%s);\n", val);
		break;
	case VARLINK_FLOAT:
		fprintf(f, "json_object_new_double(%s);\n", val);
		break;
	case VARLINK_STRING:
		fprintf(f, "json_object_new_string(%s);\n", val);
		break;
	case VARLINK_ARRAY:
		fprintf(f, "json_object_new_array();\n");

		fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n",
			nested_accessor);

		nested_name = format_str("%s_entry", name);
		print_indent(f, indent + 1);
		gen_type_ref(f, nested_name, t->inner);
		fprintf(f, "item = %sdata[i];\n", nested_accessor);
		gen_type_encoder_code(f, indent + 1, nested_name, "raw_item", "item", t->inner);
		free(nested_name);

		fprintf_indent(f, indent, "	json_object_array_add(%s, raw_item);\n",
			out_expr);

		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_MAP:
		fprintf(f, "json_object_new_object();\n");

		fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n",
			nested_accessor);

		nested_name = format_str("%s_value", name);
		fprintf_indent(f, indent, "	MAYBE_UNUSED ");
		gen_type_ref(f, nested_name, t->inner);
		fprintf(f, "value = %sdata[i].value;\n", nested_accessor);
		gen_type_encoder_code(f, indent + 1, nested_name, "raw_value", "value", t->inner);
		free(nested_name);

		fprintf_indent(f, indent, "	json_object_object_add(%s, %sdata[i].key, raw_value);\n",
			out_expr, nested_accessor);

		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_OBJECT:
		fprintf(f, "json_object_get(%s);\n", val);
		break;
	}
	free(val);
	free(nested_accessor);

	if (t->nullable) {
		indent--;
		fprintf_indent(f, indent, "}\n");
	}
}

static void gen_struct_encoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s) {
	for (size_t i = 0; i < s->fields_len; i++) {
		const char *field_name = s->field_names[i];
		const struct varlink_type *t = &s->field_types[i];

		char *nested_name = format_str("%s_%s", name, field_name);
		char *in_expr = format_str("%s%s", accessor, escape_keyword(field_name));
		char *out_expr = format_str("raw_%s", field_name);

		gen_type_encoder_code(f, indent, nested_name, out_expr, in_expr, t);

		fprintf_indent(f, indent, "json_object_object_add(raw, \"%s\", %s);\n",
			field_name, out_expr);

		free(nested_name);
		free(in_expr);
		free(out_expr);
	}
}

static void gen_struct_encoder(FILE *f, const char *name, const struct varlink_struct *s) {
	fprintf(f, "MAYBE_UNUSED\n");
	fprintf(f, "static struct json_object *%s_encode(const struct %s_%s *v) {\n",
		name, prefix, name);
	fprintf(f, "	struct json_object *raw = json_object_new_object();\n");
	gen_struct_encoder_code(f, 1, name, "v->", s);
	fprintf(f, "	return raw;\n");
	fprintf(f, "}\n");
}

static void gen_enum_encoder(FILE *f, const char *name, const struct varlink_enum *e) {
	fprintf(f, "MAYBE_UNUSED\n");
	fprintf(f, "static struct json_object *%s_encode(enum %s_%s v) {\n",
		name, prefix, name);
	fprintf(f, "	struct json_object *raw = NULL;\n");
	gen_enum_encoder_code(f, 1, name, "raw", "v", e);
	fprintf(f, "	return raw;\n");
	fprintf(f, "}\n");
}

static void gen_type_alias_encoder(FILE *f, const struct varlink_type_alias *type_alias) {
	switch (type_alias->type.kind) {
	case VARLINK_STRUCT:
		gen_struct_encoder(f, type_alias->name, &type_alias->type.struct_);
		break;
	case VARLINK_ENUM:
		gen_enum_encoder(f, type_alias->name, &type_alias->type.enum_);
		break;
	default:
		abort(); // unreachable
	}
}

static void gen_enum_decoder_code(FILE *f, int indent, const char *name, const char *val, const struct varlink_enum *e) {
	for (size_t i = 0; i < e->entries_len; i++) {
		const char *entry = e->entries[i];

		if (i == 0) {
			print_indent(f, indent);
		}
		fprintf(f, "if (strcmp(raw, \"%s\") == 0) {\n", entry);
		fprintf_indent(f, indent, "	%s = %s_%s_%s;\n", val, prefix, name, entry);
		fprintf_indent(f, indent, "}");
		if (i == e->entries_len - 1) {
			fprintf(f, "\n");
		} else {
			fprintf(f, " else ");
		}
	}

	// TODO: should an unknown value be an error?
}

static const char *json_type_from_varlink_kind(enum varlink_kind kind) {
	switch (kind) {
	case VARLINK_STRUCT:
		return "json_type_object";
	case VARLINK_ENUM:
		return "json_type_string";
	case VARLINK_NAME:
		return NULL; // too early to tell
	case VARLINK_BOOL:
		return "json_type_boolean";
	case VARLINK_INT:
		return "json_type_int";
	case VARLINK_FLOAT:
		return "json_type_double";
	case VARLINK_STRING:
		return "json_type_string";
	case VARLINK_OBJECT:
		return NULL; // can be anything
	case VARLINK_ARRAY:
		return "json_type_array";
	case VARLINK_MAP:
		return "json_type_object";
	}
	abort(); // unreachable
}

static void gen_struct_decoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s);

static void gen_type_decoder_code(FILE *f, int indent, const char *name, const char *out_expr, const char *obj_expr, const struct varlink_type *t) {
	if (t->nullable) {
		fprintf_indent(f, indent, "if (%s != NULL) {\n", obj_expr);
		indent++;
	} else {
		fprintf_indent(f, indent, "if (%s == NULL) {\n", obj_expr);
		fprintf_indent(f, indent, "	return false;\n");
		fprintf_indent(f, indent, "}\n");
	}

	const char *json_type = json_type_from_varlink_kind(t->kind);
	if (json_type != NULL) {
		fprintf_indent(f, indent, "if (json_object_get_type(%s) != %s) {\n",
			obj_expr, json_type);
		fprintf_indent(f, indent, "	return false;\n");
		fprintf_indent(f, indent, "}\n");
	}

	if (t->nullable && t->kind != VARLINK_STRING) {
		fprintf_indent(f, indent, "%s = calloc(1, sizeof(*%s));\n",
			out_expr, out_expr);
	}

	char *val = format_str("%s%s",
		t->nullable && t->kind != VARLINK_STRING ? "*" : "",
		out_expr);

	char *nested_accessor;
	if (t->nullable) {
		nested_accessor = format_str("%s->", out_expr);
	} else {
		nested_accessor = format_str("%s.", out_expr);
	}

	char *nested_name;
	switch (t->kind) {
	case VARLINK_STRUCT:
		if (t->struct_.fields_len == 0) {
			break;
		}
		fprintf_indent(f, indent, "{\n");
		fprintf_indent(f, indent, "	struct json_object *obj = %s;\n", obj_expr);
		fprintf(f, "\n");

		gen_struct_decoder_code(f, indent + 1, name, nested_accessor, &t->struct_);

		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_ENUM:
		fprintf_indent(f, indent, "const char *raw = json_object_get_string(%s);\n",
			obj_expr);

		gen_enum_decoder_code(f, indent, name, val, &t->enum_);
		break;
	case VARLINK_NAME:
		fprintf_indent(f, indent, "if (!%s_decode(%s%s, %s)) {\n",
			t->name,
			t->nullable ? "" : "&",
			out_expr, obj_expr);
		fprintf_indent(f, indent, "	return false;\n");
		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_BOOL:
		fprintf_indent(f, indent, "%s = json_object_get_boolean(%s);\n",
			val, obj_expr);
		break;
	case VARLINK_INT:
		fprintf_indent(f, indent, "%s = (int)json_object_get_int64(%s);\n",
			val, obj_expr);
		break;
	case VARLINK_FLOAT:
		fprintf_indent(f, indent, "%s = json_object_get_double(%s);\n",
			val, obj_expr);
		break;
	case VARLINK_STRING:
		fprintf_indent(f, indent, "%s = strdup(json_object_get_string(%s));\n",
			val, obj_expr);
		break;
	case VARLINK_ARRAY:
		fprintf_indent(f, indent, "size_t %s_len = json_object_array_length(%s);\n",
			obj_expr, obj_expr);
		fprintf_indent(f, indent, "%sdata = calloc(%s_len, sizeof(%sdata[0]));\n",
			nested_accessor, obj_expr, nested_accessor);
		fprintf_indent(f, indent, "if (%sdata == NULL) {\n", nested_accessor);
		fprintf_indent(f, indent, "	return false;\n");
		fprintf_indent(f, indent, "}\n");
		fprintf_indent(f, indent, "for (size_t i = 0; i < %s_len; i++) {\n",
			obj_expr);
		fprintf_indent(f, indent, "	struct json_object *item_obj = json_object_array_get_idx(%s, i);\n",
			obj_expr);

		nested_name = format_str("%s_entry", name);
		print_indent(f, indent + 1);
		gen_type_ref(f, nested_name, t->inner);
		fprintf(f, "item = {0};\n");
		gen_type_decoder_code(f, indent + 1, nested_name, "item", "item_obj", t->inner);
		fprintf_indent(f, indent, "	%sdata[i] = item;\n", nested_accessor);
		free(nested_name);

		fprintf_indent(f, indent, "	%slen++;\n", nested_accessor);
		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_MAP:
		fprintf_indent(f, indent, "size_t %s_len = (size_t)json_object_object_length(%s);\n",
			obj_expr, obj_expr);
		fprintf_indent(f, indent, "%sdata = calloc(%s_len, sizeof(%sdata[0]));\n",
			nested_accessor, obj_expr, nested_accessor);
		fprintf_indent(f, indent, "if (%sdata == NULL) {\n", nested_accessor);
		fprintf_indent(f, indent, "	return false;\n");
		fprintf_indent(f, indent, "}\n");
		fprintf_indent(f, indent, "struct json_object_iter %s_iter;\n",
			obj_expr);
		fprintf_indent(f, indent, "json_object_object_foreachC(%s, %s_iter) {\n",
			obj_expr, obj_expr);

		fprintf_indent(f, indent, "	char *key = strdup(%s_iter.key);\n",
			obj_expr);
		fprintf_indent(f, indent, "	if (key == NULL) {\n");
		fprintf_indent(f, indent, "		return false;\n");
		fprintf_indent(f, indent, "	}\n");
		fprintf_indent(f, indent, "	%sdata[%slen].key = key;\n",
			nested_accessor, nested_accessor);
		fprintf(f, "\n");

		char *nested_obj_expr = format_str("%s_iter.val", obj_expr);
		nested_name = format_str("%s_value", name);
		print_indent(f, indent + 1);
		gen_type_ref(f, nested_name, t->inner);
		fprintf(f, "value = {0};\n");
		gen_type_decoder_code(f, indent + 1, nested_name, "value", nested_obj_expr, t->inner);
		fprintf_indent(f, indent, "	%sdata[%slen].value = value;\n",
			nested_accessor, nested_accessor);
		free(nested_obj_expr);
		free(nested_name);

		fprintf_indent(f, indent, "	%slen++;\n", nested_accessor);
		fprintf_indent(f, indent, "}\n");
		break;
	case VARLINK_OBJECT:
		fprintf_indent(f, indent, "%s = json_object_get(%s);\n",
			val, obj_expr);
		break;
	}
	free(val);
	free(nested_accessor);

	if (t->nullable) {
		indent--;
		fprintf_indent(f, indent, "}\n");
	}
}

static void gen_struct_decoder_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s) {
	for (size_t i = 0; i < s->fields_len; i++) {
		const char *field_name = s->field_names[i];
		const struct varlink_type *t = &s->field_types[i];

		char *nested_name = format_str("%s_%s", name, field_name);
		char *out_expr = format_str("%s%s", accessor, escape_keyword(field_name));
		char *in_expr = format_str("raw_%s", field_name);

		if (i > 0) {
			fprintf(f, "\n");
		}
		fprintf_indent(f, indent,
			"struct json_object *raw_%s = json_object_object_get(obj, \"%s\");\n",
			field_name, field_name);
		gen_type_decoder_code(f, indent, nested_name, out_expr, in_expr, t);

		free(nested_name);
		free(out_expr);
		free(in_expr);
	}
}

static void gen_struct_decoder(FILE *f, const char *name, const struct varlink_struct *s) {
	// Note: we treat null like an empty object because the "parameters" field
	// in method calls, replies and errors require it
	fprintf(f, "MAYBE_UNUSED\n");
	fprintf(f, "static bool %s_decode(struct %s_%s *out, struct json_object *obj) {\n",
		name, prefix, name);
	fprintf(f,
		"	if (json_object_get_type(obj) != json_type_object && obj != NULL) {\n"
		"		return false;\n"
		"	}\n"
		"\n");
	gen_struct_decoder_code(f, 1, name, "out->", s);
	fprintf(f, "\n");
	fprintf(f, "	return true;\n");
	fprintf(f, "}\n");
}

static void gen_enum_decoder(FILE *f, const char *name, const struct varlink_enum *e) {
	fprintf(f, "MAYBE_UNUSED\n");
	fprintf(f, "static bool %s_decode(enum %s_%s *out, struct json_object *obj) {\n",
		name, prefix, name);
	fprintf(f,
		"	if (json_object_get_type(obj) != json_type_string) {\n"
		"		return false;\n"
		"	}\n"
		"	const char *raw = json_object_get_string(obj);\n");
	gen_enum_decoder_code(f, 1, name, "*out", e);
	fprintf(f, "	return true;\n");
	fprintf(f, "}\n");
}

static void gen_type_alias_decoder(FILE *f, const struct varlink_type_alias *type_alias) {
	switch (type_alias->type.kind) {
	case VARLINK_STRUCT:
		gen_struct_decoder(f, type_alias->name, &type_alias->type.struct_);
		break;
	case VARLINK_ENUM:
		gen_enum_decoder(f, type_alias->name, &type_alias->type.enum_);
		break;
	default:
		abort(); // unreachable
	}
}

static void gen_struct_finisher_prototype(FILE *f, const char *name) {
	fprintf(f, "void %s_%s_finish(struct %s_%s *v)", prefix, name, prefix, name);
}

static void gen_struct_finisher_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s);

static void gen_type_finisher_code(FILE *f, int indent, const char *name, const char *expr, const struct varlink_type *t) {
	if (t->nullable) {
		fprintf_indent(f, indent, "if (%s != NULL) {\n", expr);
		indent++;
	}

	char *nested_accessor;
	if (t->nullable) {
		nested_accessor = format_str("%s->", expr);
	} else {
		nested_accessor = format_str("%s.", expr);
	}

	char *nested_name;
	switch (t->kind) {
	case VARLINK_STRUCT:;
		gen_struct_finisher_code(f, indent, name, nested_accessor, &t->struct_);
		break;
	case VARLINK_NAME:;
		const struct varlink_type *ref_type = find_type(t->name);
		if (ref_type->kind == VARLINK_STRUCT) {
			fprintf_indent(f, indent, "%s_%s_finish(%s%s);\n",
				prefix, t->name,
				t->nullable ? "" : "&",
				expr);
		}
		break;
	case VARLINK_ENUM:
	case VARLINK_BOOL:
	case VARLINK_INT:
	case VARLINK_FLOAT:
		break; // nothing to do
	case VARLINK_STRING:
		break; // handled below
	case VARLINK_ARRAY:
		fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n",
			nested_accessor);

		nested_name = format_str("%s_entry", name);
		fprintf_indent(f, indent, "	MAYBE_UNUSED ");
		gen_type_ref(f, nested_name, t->inner);
		fprintf(f, "entry = %sdata[i];\n", nested_accessor);
		gen_type_finisher_code(f, indent + 1, nested_name, "entry", t->inner);
		free(nested_name);

		fprintf_indent(f, indent, "}\n");

		fprintf_indent(f, indent, "free(%sdata);\n", nested_accessor);
		break;
	case VARLINK_MAP:
		fprintf_indent(f, indent, "for (size_t i = 0; i < %slen; i++) {\n",
			nested_accessor);

		fprintf_indent(f, indent, "	free(%sdata[i].key);\n",
			nested_accessor);

		nested_name = format_str("%s_value", name);
		fprintf_indent(f, indent, "	MAYBE_UNUSED ");
		gen_type_ref(f, nested_name, t->inner);
		fprintf(f, "value = %sdata[i].value;\n", nested_accessor);
		gen_type_finisher_code(f, indent + 1, nested_name, "value", t->inner);
		free(nested_name);

		fprintf_indent(f, indent, "}\n");

		fprintf_indent(f, indent, "free(%sdata);\n", nested_accessor);
		break;
	case VARLINK_OBJECT:
		fprintf_indent(f, indent, "json_object_put(%s);\n", expr);
		break;
	}
	free(nested_accessor);

	if (t->nullable || t->kind == VARLINK_STRING) {
		fprintf_indent(f, indent, "free(%s);\n", expr);
	}

	if (t->nullable) {
		indent--;
		fprintf_indent(f, indent, "}\n");
	}
}

static void gen_struct_finisher_code(FILE *f, int indent, const char *name, const char *accessor, const struct varlink_struct *s) {
	for (size_t i = 0; i < s->fields_len; i++) {
		const char *field_name = s->field_names[i];
		const struct varlink_type *t = &s->field_types[i];

		char *nested_name = format_str("%s_%s", name, field_name);
		char *expr = format_str("%s%s", accessor, escape_keyword(field_name));
		gen_type_finisher_code(f, indent, nested_name, expr, t);
		free(nested_name);
		free(expr);
	}
}

static void gen_struct_finisher(FILE *f, const char *name, const struct varlink_struct *s) {
	gen_struct_finisher_prototype(f, name);
	fprintf(f, " {\n");
	gen_struct_finisher_code(f, 1, name, "v->", s);
	fprintf(f, "}\n");
}

static void gen_error_header(FILE *f, const struct varlink_error *error) {
	const char *name = error->name;
	const struct varlink_struct *s = &error->struct_;

	if (error->description != NULL) {
		print_description(f, 0, error->description);
	}

	char *id = format_str("error_%s", name);
	gen_struct(f, 0, id, s);
	fprintf(f, ";\n");
	free(id);

	fprintf(f, "/** Try to convert a struct vali_error into a %s error. */\n", name);
	fprintf(f, "bool %s_error_%s_from(struct %s_error_%s *out, const struct vali_error *err);\n",
		prefix, name, prefix, name);

	fprintf(f, "/** Close a service call with a %s error. */\n", name);
	fprintf(f, "void %s_error_%s_close_service_call(struct vali_service_call *call, const struct %s_error_%s *params);\n",
		prefix, name, prefix, name);
}

static void gen_method_call_prototype(FILE *f, const struct varlink_method *method) {
	fprintf(f, "bool %s_%s(struct vali_client *c, const struct %s_%s_in *in, struct %s_%s_out *out, struct vali_error *err)",
		prefix, method->name, prefix, method->name, prefix, method->name);
}

static void gen_method_call_more_prototype(FILE *f, const struct varlink_method *method) {
	fprintf(f, "struct %s_%s_client_call %s_%s_more(struct vali_client *c, const struct %s_%s_in *in)",
		prefix, method->name, prefix, method->name, prefix, method->name);
}

static void gen_method_call_oneway_prototype(FILE *f, const struct varlink_method *method) {
	fprintf(f, "bool %s_%s_oneway(struct vali_client *c, const struct %s_%s_in *in)",
		prefix, method->name, prefix, method->name);
}

static void gen_method_header(FILE *f, const struct varlink_method *method) {
	char *name = format_str("%s_in", method->name);
	fprintf(f, "/** Input for %s calls. */\n", method->name),
	gen_struct(f, 0, name, &method->in);
	fprintf(f, ";\n");
	free(name);

	name = format_str("%s_out", method->name);
	fprintf(f, "/** Output for %s calls. */\n", method->name),
	gen_struct(f, 0, name, &method->out);
	fprintf(f, ";\n");
	fprintf(f, "/** Release resources held by %s call output. */\n", method->name),
	gen_struct_finisher_prototype(f, name);
	fprintf(f, ";\n");
	free(name);

	if (method->description != NULL) {
		print_description(f, 0, method->description);
	} else {
		fprintf(f, "/** Perform a %s client call. */\n", method->name);
	}
	gen_method_call_prototype(f, method);
	fprintf(f, ";\n");

	fprintf(f, "/** A %s call for a client. */\n", method->name);
	fprintf(f, "struct %s_%s_client_call {\n",
		prefix, method->name);
	fprintf(f, "	struct vali_client_call *base;\n");
	fprintf(f, "};\n");

	fprintf(f, "/** Send a request for a %s client call, indicating that multiple replies are expected. */\n", method->name);
	gen_method_call_more_prototype(f, method);
	fprintf(f, ";\n");

	fprintf(f, "/** Wait for a %s reply. */\n", method->name);
	fprintf(f, "bool %s_%s_client_call_wait(struct %s_%s_client_call call, struct %s_%s_out *out, struct vali_error *err);\n",
		prefix, method->name, prefix, method->name, prefix, method->name);

	fprintf(f, "/** Send a request for a %s client call, indicating that no reply is expected. */", method->name);
	gen_method_call_oneway_prototype(f, method);
	fprintf(f, ";\n");

	fprintf(f, "/** A %s call for a service. */\n", method->name);
	fprintf(f, "struct %s_%s_service_call {\n",
		prefix, method->name);
	fprintf(f, "	struct vali_service_call *base;\n");
	fprintf(f, "};\n");

	fprintf(f, "/** Close a %s service call with a final reply. */\n", method->name);
	fprintf(f, "void %s_%s_close_with_reply(struct %s_%s_service_call call, const struct %s_%s_out *params);\n",
		prefix, method->name, prefix, method->name, prefix, method->name);

	fprintf(f, "/** Send a reply for a %s service call, indicating that more replies will be sent. */\n", method->name);
	fprintf(f, "void %s_%s_reply(struct %s_%s_service_call call, const struct %s_%s_out *params);\n",
		prefix, method->name, prefix, method->name, prefix, method->name);
}

static void gen_header(FILE *f, const struct varlink_interface *iface) {
	fprintf(f, "#ifndef ");
	print_iface_include_guard(f, iface->name);
	fprintf(f, "\n");
	fprintf(f, "#define ");
	print_iface_include_guard(f, iface->name);
	fprintf(f, "\n\n");

	fprintf(f,
		"#include <stdbool.h>\n"
		"#include <stddef.h>\n"
		"\n"
		"struct json_object;\n"
		"struct vali_client;\n"
		"struct vali_error;\n"
		"struct vali_service_call;\n"
		"struct vali_registry;\n"
		"\n");

	if (iface->description != NULL) {
		print_description(f, 0, iface->description);
		fprintf(f, "\n");
	}

	for (size_t i = 0; i < iface->type_aliases_len; i++) {
		gen_type_alias(f, &iface->type_aliases[i]);
		fprintf(f, "\n");
	}

	for (size_t i = 0; i < iface->errors_len; i++) {
		gen_error_header(f, &iface->errors[i]);
		fprintf(f, "\n");
	}

	for (size_t i = 0; i < iface->methods_len; i++) {
		gen_method_header(f, &iface->methods[i]);
		fprintf(f, "\n");
	}

	fprintf(f, "/** Handler for %s. */\n", iface->name),
	fprintf(f, "struct %s_handler {\n", prefix);
	for (size_t i = 0; i < iface->methods_len; i++) {
		const struct varlink_method *method = &iface->methods[i];
		if (method->description != NULL) {
			print_description(f, 1, method->description);
		}
		fprintf(f, "	void (*%s)(struct %s_%s_service_call call, const struct %s_%s_in *in);\n",
			method->name, prefix, method->name, prefix, method->name);
	}
	fprintf(f, "};\n\n");

	fprintf(f, "/** Get a service call handler from a %s handler. */\n", iface->name);
	fprintf(f, "struct vali_service_call_handler %s_get_call_handler(const struct %s_handler *handler);\n\n",
		prefix, prefix);

	fprintf(f, "/** Description of the %s interface. */\n", iface->name);
	fprintf(f, "extern const struct vali_registry_interface %s_interface;\n\n",
		prefix);

	fprintf(f, "#endif\n");
}

static void gen_error_code(FILE *f, const struct varlink_error *error) {
	const char *name = error->name;
	const struct varlink_struct *s = &error->struct_;

	char *id = format_str("error_%s", name);
	gen_struct_encoder(f, id, s);
	gen_struct_decoder(f, id, s);
	free(id);

	fprintf(f, "bool %s_error_%s_from(struct %s_error_%s *out, const struct vali_error *err) {\n",
		prefix, name, prefix, name);
	fprintf(f, "	if (err->name == NULL || strcmp(err->name, \"%s.%s\") != 0) {\n",
		iface->name, name);
	fprintf(f, "		return false;\n");
	fprintf(f, "	}\n");
	fprintf(f, "	return error_%s_decode(out, err->parameters);\n",
		name);
	fprintf(f, "}\n");

	fprintf(f, "void %s_error_%s_close_service_call(struct vali_service_call *call, const struct %s_error_%s *params) {\n",
		prefix, name, prefix, name);
	fprintf(f, "	struct json_object *obj = error_%s_encode(params);\n",
		name);
	fprintf(f, "	vali_service_call_close_with_error(call, \"%s.%s\", obj);\n",
		iface->name, name);
	fprintf(f, "}\n");
}

static void gen_method_in_out_code(FILE *f, const char *method, const char *type, const struct varlink_struct *s) {
	char *name = format_str("%s_%s", method, type);
	gen_struct_encoder(f, name, s);
	fprintf(f, "\n");
	gen_struct_decoder(f, name, s);
	fprintf(f, "\n");
	if (strcmp(type, "in") == 0) {
		fprintf(f,
			"MAYBE_UNUSED\n"
			"static ");
	} else {
		fprintf(f, "HIDDEN\n");
	}
	gen_struct_finisher(f, name, s);
	fprintf(f, "\n");
	free(name);
}

static void gen_method_code(FILE *f, const struct varlink_method *method) {
	gen_method_in_out_code(f, method->name, "in", &method->in);
	gen_method_in_out_code(f, method->name, "out", &method->out);

	fprintf(f, "HIDDEN\n");
	gen_method_call_prototype(f, method);
	fprintf(f, " {\n");
	fprintf(f, "	struct json_object *raw_in = %s_in_encode(in);\n",
		method->name);
	fprintf(f, "	struct json_object *raw_out = NULL;\n");
	fprintf(f, "	if (!vali_client_call(c, \"%s.%s\", raw_in, &raw_out, err)) {\n",
		iface->name, method->name);
	fprintf(f, "		return false;\n");
	fprintf(f, "	}\n");
	fprintf(f, "	bool ok = true;\n");
	fprintf(f, "	if (out != NULL) {\n");
	fprintf(f, "		*out = (struct %s_%s_out){0};\n", prefix, method->name);
	fprintf(f, "		ok = %s_out_decode(out, raw_out);\n",
		method->name);
	fprintf(f, "	}\n");
	fprintf(f, "	json_object_put(raw_out);\n");
	fprintf(f, "	return ok;\n");
	fprintf(f, "}\n");
	fprintf(f, "\n");

	fprintf(f, "HIDDEN\n");
	gen_method_call_more_prototype(f, method);
	fprintf(f, " {\n");
	fprintf(f, "	struct json_object *raw_in = %s_in_encode(in);\n",
		method->name);
	fprintf(f, "	struct vali_client_call *call = vali_client_call_more(c, \"%s.%s\", raw_in);\n",
		iface->name, method->name);
	fprintf(f, "	return (struct %s_%s_client_call){ .base = call };\n",
		prefix, method->name);
	fprintf(f, "}\n");
	fprintf(f, "\n");

	fprintf(f, "HIDDEN\n");
	gen_method_call_oneway_prototype(f, method);
	fprintf(f, " {\n");
	fprintf(f, "	struct json_object *raw_in = %s_in_encode(in);\n",
		method->name);
	fprintf(f, "	return vali_client_call_oneway(c, \"%s.%s\", raw_in);\n",
		iface->name, method->name);
	fprintf(f, "}\n");
	fprintf(f, "\n");

	fprintf(f, "HIDDEN\n");
	fprintf(f, "bool %s_%s_client_call_wait(struct %s_%s_client_call call, struct %s_%s_out *out, struct vali_error *err) {\n",
		prefix, method->name, prefix, method->name, prefix, method->name);
	fprintf(f, "	struct json_object *raw_out = NULL;\n");
	fprintf(f, "	if (!vali_client_call_wait(call.base, &raw_out, err)) {\n");
	fprintf(f, "		return false;\n");
	fprintf(f, "	}\n");
	fprintf(f, "	bool ok = true;\n");
	fprintf(f, "	if (out != NULL) {\n");
	fprintf(f, "		*out = (struct %s_%s_out){0};\n", prefix, method->name);
	fprintf(f, "		ok = %s_out_decode(out, raw_out);\n",
		method->name);
	fprintf(f, "	}\n");
	fprintf(f, "	json_object_put(raw_out);\n");
	fprintf(f, "	return ok;\n");
	fprintf(f, "}\n");

	fprintf(f, "HIDDEN\n");
	fprintf(f, "void %s_%s_close_with_reply(struct %s_%s_service_call call, const struct %s_%s_out *params) {\n",
		prefix, method->name, prefix, method->name, prefix, method->name);
	fprintf(f, "	struct json_object *raw = %s_out_encode(params);\n",
		method->name);
	fprintf(f, "	vali_service_call_close_with_reply(call.base, raw);\n");
	fprintf(f, "}\n");

	fprintf(f, "HIDDEN\n");
	fprintf(f, "void %s_%s_reply(struct %s_%s_service_call call, const struct %s_%s_out *params) {\n",
		prefix, method->name, prefix, method->name, prefix, method->name);
	fprintf(f, "	struct json_object *raw = %s_out_encode(params);\n",
		method->name);
	fprintf(f, "	vali_service_call_reply(call.base, raw);\n");
	fprintf(f, "}\n");
}

static void gen_code(FILE *f, const struct varlink_interface *iface, const char *header_basename, const char *def) {
	fprintf(f,
		"#include <stdlib.h>\n"
		"#include <string.h>\n"
		"\n"
		"#include <json_object.h>\n"
		"#include <linkhash.h>\n"
		"#include <vali.h>\n"
		"\n"
		"#include \"%s\"\n"
		"\n",
		header_basename);

	fprintf(f,
		"#if __STDC_VERSION__ >= 202311L\n"
		"#define MAYBE_UNUSED [[maybe_unused]]\n"
		"#elif defined(__GNUC__)\n"
		"#define MAYBE_UNUSED __attribute__((__unused__))\n"
		"#else\n"
		"#define MAYBE_UNUSED\n"
		"#endif\n\n");

	fprintf(f,
		"#define HIDDEN\n"
		"#if defined(__has_attribute)\n"
		"#if __has_attribute(visibility)\n"
		"#undef HIDDEN\n"
		"#define HIDDEN __attribute__((visibility(\"hidden\")))\n"
		"#endif\n"
		"#endif\n\n");

	for (size_t i = 0; i < iface->type_aliases_len; i++) {
		const struct varlink_type_alias *type_alias = &iface->type_aliases[i];
		gen_type_alias_encoder(f, type_alias);
		fprintf(f, "\n");
		gen_type_alias_decoder(f, type_alias);
		fprintf(f, "\n");
		if (type_alias->type.kind == VARLINK_STRUCT) {
			fprintf(f,
				"MAYBE_UNUSED\n"
				"static ");
			gen_struct_finisher(f, type_alias->name, &type_alias->type.struct_);
			fprintf(f, "\n");
		}
	}

	for (size_t i = 0; i < iface->methods_len; i++) {
		gen_method_code(f, &iface->methods[i]);
		fprintf(f, "\n");
	}

	for (size_t i = 0; i < iface->errors_len; i++) {
		gen_error_code(f, &iface->errors[i]);
		fprintf(f, "\n");
	}

	fprintf(f,
		"MAYBE_UNUSED\n"
		"static void fail_method_not_implemented(struct vali_service_call *call, const char *method) {\n"
		"	struct json_object *params = json_object_new_object();\n"
		"	json_object_object_add(params, \"method\", json_object_new_string(method));\n"
		"	vali_service_call_close_with_error(call, \"org.varlink.service.MethodNotImplemented\", params);\n"
		"}\n"
		"\n"
		"MAYBE_UNUSED\n"
		"static void fail_invalid_parameter(struct vali_service_call *call, const char *param) {\n"
		"	struct json_object *params = json_object_new_object();\n"
		"	json_object_object_add(params, \"parameter\", json_object_new_string(param));\n"
		"	vali_service_call_close_with_error(call, \"org.varlink.service.InvalidParameter\", params);\n"
		"}\n"
		"\n");

	fprintf(f,
		"static void handle_call(struct vali_service_call *call, void *user_data) {\n"
		"	const struct %s_handler *handler = user_data;\n"
		"	const char *method = vali_service_call_get_method(call);\n"
		"	struct json_object *raw_in = vali_service_call_get_parameters(call);\n",
		prefix);
	for (size_t i = 0; i < iface->methods_len; i++) {
		const struct varlink_method *method = &iface->methods[i];
		fprintf(f, "	if (strcmp(method, \"%s.%s\") == 0) {\n",
			iface->name, method->name);
		fprintf(f, "		if (handler->%s == NULL) {\n", method->name);
		fprintf(f, "			fail_method_not_implemented(call, method);\n");
		fprintf(f, "			return;\n");
		fprintf(f, "		}\n");
		fprintf(f, "		struct %s_%s_in in = {0};\n", prefix, method->name);
		fprintf(f, "		if (!%s_in_decode(&in, raw_in)) {\n", method->name);
		// TODO: set parameter name
		fprintf(f, "			fail_invalid_parameter(call, NULL);\n");
		fprintf(f, "			return;\n");
		fprintf(f, "		}\n");
		fprintf(f, "		handler->%s((struct %s_%s_service_call){call}, &in);\n",
			method->name, prefix, method->name);
		fprintf(f, "		%s_%s_in_finish(&in);\n", prefix, method->name);
		fprintf(f, "		return;\n");
		fprintf(f, "	}\n");
	}
	fprintf(f,
		"	struct json_object *params = json_object_new_object();\n"
		"	json_object_object_add(params, \"method\", json_object_new_string(method));\n"
		"	vali_service_call_close_with_error(call, \"org.varlink.service.MethodNotFound\", params);\n"
		"}\n"
		"\n");

	fprintf(f,
		"HIDDEN\n"
		"const struct vali_registry_interface %s_interface = {\n"
		"	.name = \"%s\",\n"
		"	.definition =\n"
		"		\"",
		prefix, iface->name);
	for (size_t i = 0; def[i] != '\0'; i++) {
		switch (def[i]) {
		case '\n':
			fprintf(f,
				"\\n\"\n"
				"		\"");
			continue;
		case '\\':
		case '"':
			fputc('\\', f);
			break;
		}
		fputc(def[i], f);
	}
	fprintf(f, "\",\n");
	fprintf(f, "};\n\n");

	fprintf(f,
		"HIDDEN\n"
		"struct vali_service_call_handler %s_get_call_handler(const struct %s_handler *handler) {\n"
		"	return (struct vali_service_call_handler){\n"
		"		.func = handle_call,\n"
		"		.user_data = (void *)handler,\n"
		"	};\n"
		"}\n"
		"\n",
		prefix, prefix);
}

static char *read_full(FILE *f) {
	char buf[1024];
	struct array arr = {0};
	while (1) {
		size_t n = fread(buf, 1, sizeof(buf), f);
		if (n == 0) {
			if (feof(f)) {
				break;
			} else {
				goto err;
			}
		}

		char *dst = array_add(&arr, n);
		if (dst == NULL) {
			goto err;
		}
		memcpy(dst, buf, n);
	}

	char *end = array_add(&arr, 1);
	if (end == NULL) {
		goto err;
	}
	*end = '\0';

	return arr.data;

err:
	array_finish(&arr);
	return NULL;
}

const char usage[] = "usage: vali generate [options...] <definition> <header> <code>\n";

int main(int argc, char *argv[]) {
	if (argc < 2 || strcmp(argv[1], "generate") != 0) {
		fprintf(stderr, "%s", usage);
		return 1;
	}

	while (1) {
		const struct option options[] = {
			{ .name = "prefix", .has_arg = required_argument },
			{0},
		};

		int i = -1;
		int opt = getopt_long(argc - 1, &argv[1], "", options, &i);
		if (opt < 0) {
			break;
		} else if (opt != 0) {
			return 1;
		}
		const char *optname = options[i].name;

		if (strcmp(optname, "prefix") == 0) {
			prefix = strdup(optarg);
		} else {
			abort(); // unreachable
		}
	}

	if (optind + 4 != argc) {
		fprintf(stderr, "%s", usage);
		return 1;
	}

	const char *input_filename = argv[optind + 1];
	const char *header_filename = argv[optind + 2];
	const char *code_filename = argv[optind + 3];

	FILE *f = fopen(input_filename, "r");
	if (f == NULL) {
		perror("Failed to open definition file");
		return 1;
	}

	char *def = read_full(f);
	fclose(f);
	if (def == NULL) {
		fprintf(stderr, "Failed to read interface definition file\n");
		return 1;
	}

	f = fmemopen(def, strlen(def), "r");
	if (f == NULL) {
		perror("fmemopen() failed");
		return 1;
	}

	iface = varlink_interface_read(f);
	fclose(f);
	if (iface == NULL) {
		fprintf(stderr, "Failed to parse interface definition\n");
		return 1;
	}

	if (prefix == NULL) {
		prefix = strdup(iface->name);
		for (size_t i = 0; prefix[i] != '\0'; i++) {
			if (prefix[i] == '-' || prefix[i] == '.') {
				prefix[i] = '_';
			}
		}
	}

	FILE *header_file = fopen(header_filename, "w");
	if (header_file == NULL) {
		perror("Failed to open header file");
		return 1;
	}

	FILE *code_file = fopen(code_filename, "w");
	if (code_file == NULL) {
		perror("Failed to open code file");
		return 1;
	}

	const char *last_slash = strrchr(header_filename, '/');
	const char *header_basename;
	if (last_slash != NULL) {
		header_basename = &last_slash[1];
	} else {
		header_basename = header_filename;
	}

	gen_header(header_file, iface);
	gen_code(code_file, iface, header_basename, def);

	free(prefix);
	varlink_interface_destroy(iface);
	free(def);

	if (fclose(header_file) != 0) {
		perror("Failed to close header file");
		return 1;
	}
	if (fclose(code_file) != 0) {
		perror("Failed to close code file");
		return 1;
	}

	return 0;
}
