#include <assert.h>
#include <poll.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>

#include <json_object.h>
#include <vali.h>

#include "certification-gen.h"

struct service_state {
	uint64_t prev_client_num;

	// TODO: handle multiple clients
	char *client_id;
	int prev_test_num;
};

static const struct cert_Test01_out service_test01_out = {
	.bool_ = true,
};

static const struct cert_Test02_out service_test02_out = {
	.int_ = 42,
};

static const struct cert_Test03_out service_test03_out = {
	.float_ = 12.34,
};

static const struct cert_Test04_out service_test04_out = {
	.string = "foo",
};

static const struct cert_Test05_out service_test05_out = {
	.bool_ = false,
	.int_ = 12,
	.float_ = 34.56,
	.string = "bar",
};

static const struct cert_Test06_out service_test06_out = {
	.struct_ = {
		.bool_ = true,
		.int_ = 98,
		.float_ = 76.54,
		.string = "baz",
	},
};

static const struct cert_Test07_out service_test07_out = {
	.map = {
		.data = (struct cert_Test07_out_map_entry[]){
			{ .key = "Julie", .value = "Renate" },
			{ .key = "Aksel", .value = "Anders" },
			{ .key = "Eivind", .value = "Hebert" },
		},
		.len = 3,
	},
};

static const struct cert_Test08_out service_test08_out = {
	.set = {
		.data = (struct cert_Test08_out_set_entry[]){
			{ .key = "Nora" },
			{ .key = "Gustav" },
			{ .key = "Lilleaas" },
			{ .key = "Rachel" },
		},
		.len = 4,
	},
};

static struct cert_Test09_out service_test09_out = {
	.mytype = {
		.object = NULL,
		.enum_ = cert_MyType_enum_two,
		.struct_ = {
			.first = 42,
			.second = "foo",
		},
		.array = {
			.data = (char *[]){
				"Anders",
				"Thomas",
				"Rebekka",
				"Tove",
				"Renate",
			},
			.len = 5,
		},
		.dictionary = {
			.data = (struct cert_MyType_dictionary_entry[]){
				{ .key = "Julie", .value = "Renate" },
				{ .key = "Aksel", .value = "Anders" },
				{ .key = "Eivind", .value = "Hebert" },
			},
			.len = 3,
		},
		.stringset = {
			.data = (struct cert_MyType_stringset_entry[]){
				{ .key = "Nora" },
				{ .key = "Gustav" },
				{ .key = "Lilleaas" },
				{ .key = "Rachel" },
			},
			.len = 4,
		},
		.nullable = NULL,
		.nullable_array_struct = &(struct cert_MyType_nullable_array_struct){
			.data = (struct cert_MyType_nullable_array_struct_entry[]){
				{ .first = 2006, .second = "Reprise" },
				{ .first = 2011, .second = "Oslo, 31. august" },
				{ .first = 2021, .second = "Verdens verste menneske" },
			},
			.len = 3,
		},
		.interface = {
			.foo = &(struct cert_Interface_foo){
				.data = (struct cert_Interface_foo_entry *[]){
					NULL,
					&(struct cert_Interface_foo_entry){
						.data = (struct cert_Interface_foo_entry_entry[]){
							{ .key = "one", .value = cert_Interface_foo_entry_value_baz },
						},
						.len = 1,
					},
					&(struct cert_Interface_foo_entry){
						.data = (struct cert_Interface_foo_entry_entry[]){
							{ .key = "two", .value = cert_Interface_foo_entry_value_foo },
							{ .key = "three", .value = cert_Interface_foo_entry_value_bar },
						},
						.len = 2,
					},
					NULL,
				},
				.len = 4,
			},
			.anon = {
				.foo = true,
				.bar = false,
			},
		},
	},
};

static char *const service_test10_replies[] = {
	"Anders",
	"Thomas",
	"Rebekka",
	"Tove",
	"Renate",
};

static const size_t service_test10_replies_len = sizeof(service_test10_replies) / sizeof(service_test10_replies[0]);

static const char usage[] =
	"usage: certification client|service <path>    Run the client/service against an external implementation\n"
	"       certification self-test                Run the client against the internal service\n";

static void print_client_error(const char *method, const struct vali_error *err) {
	fprintf(stderr, "%s() failed", method);
	if (err->name != NULL) {
		const char *raw_params = json_object_to_json_string_ext(err->parameters, JSON_C_TO_STRING_PRETTY);
		fprintf(stderr, ": %s %s", err->name, raw_params);
	}
	fprintf(stderr, "\n");
}

static bool run_client(struct vali_client *client) {
	struct vali_error err = {0};
	struct cert_Start_out start_out = {0};
	if (!cert_Start(client, NULL, &start_out, &err)) {
		print_client_error("Start", &err);
		return false;
	}
	char *client_id = start_out.client_id;

	const struct cert_Test01_in test01_in = {
		.client_id = client_id,
	};
	struct cert_Test01_out test01_out = {0};
	if (!cert_Test01(client, &test01_in, &test01_out, &err)) {
		print_client_error("Test01", &err);
		return false;
	}

	const struct cert_Test02_in test02_in = {
		.client_id = client_id,
		.bool_ = test01_out.bool_,
	};
	struct cert_Test02_out test02_out = {0};
	if (!cert_Test02(client, &test02_in, &test02_out, &err)) {
		print_client_error("Test02", &err);
		return false;
	}

	const struct cert_Test03_in test03_in = {
		.client_id = client_id,
		.int_ = test02_out.int_,
	};
	struct cert_Test03_out test03_out = {0};
	if (!cert_Test03(client, &test03_in, &test03_out, &err)) {
		print_client_error("Test03", &err);
		return false;
	}

	const struct cert_Test04_in test04_in = {
		.client_id = client_id,
		.float_ = test03_out.float_,
	};
	struct cert_Test04_out test04_out = {0};
	if (!cert_Test04(client, &test04_in, &test04_out, &err)) {
		print_client_error("Test04", &err);
		return false;
	}

	const struct cert_Test05_in test05_in = {
		.client_id = client_id,
		.string = test04_out.string,
	};
	struct cert_Test05_out test05_out = {0};
	if (!cert_Test05(client, &test05_in, &test05_out, &err)) {
		print_client_error("Test05", &err);
		return false;
	}

	const struct cert_Test06_in test06_in = {
		.client_id = client_id,
		.bool_ = test05_out.bool_,
		.int_ = test05_out.int_,
		.float_ = test05_out.float_,
		.string = test05_out.string,
	};
	struct cert_Test06_out test06_out = {0};
	if (!cert_Test06(client, &test06_in, &test06_out, &err)) {
		print_client_error("Test06", &err);
		return false;
	}

	const struct cert_Test07_in test07_in = {
		.client_id = client_id,
		.struct_ = {
			.bool_ = test06_out.struct_.bool_,
			.int_ = test06_out.struct_.int_,
			.float_ = test06_out.struct_.float_,
			.string = test06_out.struct_.string,
		},
	};
	struct cert_Test07_out test07_out = {0};
	if (!cert_Test07(client, &test07_in, &test07_out, &err)) {
		print_client_error("Test07", &err);
		return false;
	}

	const struct cert_Test08_in test08_in = {
		.client_id = client_id,
		.map = {
			.data = (struct cert_Test08_in_map_entry *)test07_out.map.data,
			.len = test07_out.map.len,
		},
	};
	struct cert_Test08_out test08_out = {0};
	if (!cert_Test08(client, &test08_in, &test08_out, &err)) {
		print_client_error("Test08", &err);
		return false;
	}

	const struct cert_Test09_in test09_in = {
		.client_id = client_id,
		.set = {
			.data = (struct cert_Test09_in_set_entry *)test08_out.set.data,
			.len = test08_out.set.len,
		},
	};
	struct cert_Test09_out test09_out = {0};
	if (!cert_Test09(client, &test09_in, &test09_out, &err)) {
		print_client_error("Test09", &err);
		return false;
	}

	struct cert_Test10_in test10_in = {
		.client_id = client_id,
		.mytype = test09_out.mytype,
	};
	struct cert_Test10_client_call call = cert_Test10_more(client, &test10_in);
	if (call.base == NULL) {
		fprintf(stderr, "Test10() failed: send error\n");
		return false;
	}
	char *test10_replies[64] = {0};
	size_t test10_replies_len = 0;
	while (!vali_client_call_is_done(call.base)) {
		struct cert_Test10_out test10_out = {0};
		if (!cert_Test10_client_call_wait(call, &test10_out, &err)) {
			print_client_error("Test10", &err);
			return false;
		}
		assert(test10_replies_len < sizeof(test10_replies) / sizeof(test10_replies[0]));
		test10_replies[test10_replies_len++] = test10_out.string;
	}
	vali_client_call_destroy(call.base);

	struct cert_Test11_in test11_in = {
		.client_id = client_id,
		.last_more_replies = {
			.data = test10_replies,
			.len = test10_replies_len,
		},
	};
	if (!cert_Test11_oneway(client, &test11_in)) {
		fprintf(stderr, "Test11() failed: send error\n");
		return false;
	}

	struct cert_End_in end_in = {
		.client_id = client_id,
	};
	struct cert_End_out end_out = {0};
	if (!cert_End(client, &end_in, &end_out, &err)) {
		print_client_error("End", &err);
		return false;
	}
	if (!end_out.all_ok) {
		fprintf(stderr, "End() indicates missing calls\n");
	}

	cert_End_out_finish(&end_out);
	for (size_t i = 0; i < test10_replies_len; i++) {
		free(test10_replies[i]);
	}
	cert_Test09_out_finish(&test09_out);
	cert_Test08_out_finish(&test08_out);
	cert_Test07_out_finish(&test07_out);
	cert_Test06_out_finish(&test06_out);
	cert_Test05_out_finish(&test05_out);
	cert_Test04_out_finish(&test04_out);
	cert_Test03_out_finish(&test03_out);
	cert_Test02_out_finish(&test02_out);
	cert_Test01_out_finish(&test01_out);
	cert_Start_out_finish(&start_out);
	vali_client_destroy(client);
	return true;
}

static struct service_state *service_state_from_call(struct vali_service_call *call) {
	return vali_service_get_user_data(vali_service_call_get_service(call));
}

static bool service_check_client_id(struct service_state *state, const char *client_id) {
	return state->client_id != NULL && strcmp(state->client_id, client_id) == 0;
}

static bool service_bump_test_num(struct service_state *state, int test_num) {
	if (state->prev_test_num != test_num - 1) {
		return false;
	}
	state->prev_test_num = test_num;
	return true;
}

static void handle_start(struct cert_Start_service_call call, const struct cert_Start_in *in) {
	struct service_state *state = service_state_from_call(call.base);

	state->prev_client_num++;

	char client_id[128];
	snprintf(client_id, sizeof(client_id), "vali-%"PRIu64, state->prev_client_num);

	free(state->client_id);
	state->client_id = strdup(client_id);
	state->prev_test_num = 0;

	cert_Start_close_with_reply(call, &(struct cert_Start_out){
		.client_id = client_id,
	});
}

static void handle_test01(struct cert_Test01_service_call call, const struct cert_Test01_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}
	if (!service_bump_test_num(state, 1)) {
		// TODO: populate got/wants
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}
	cert_Test01_close_with_reply(call, &service_test01_out);
}

static void handle_test02(struct cert_Test02_service_call call, const struct cert_Test02_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	if (in->bool_ != service_test01_out.bool_ ||
			!service_bump_test_num(state, 2)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test02_close_with_reply(call, &service_test02_out);
}

static void handle_test03(struct cert_Test03_service_call call, const struct cert_Test03_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	if (in->int_ != service_test02_out.int_ ||
			!service_bump_test_num(state, 3)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test03_close_with_reply(call, &service_test03_out);
}

static void handle_test04(struct cert_Test04_service_call call, const struct cert_Test04_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	if (in->float_ != service_test03_out.float_ ||
			!service_bump_test_num(state, 4)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test04_close_with_reply(call, &service_test04_out);
}

static void handle_test05(struct cert_Test05_service_call call, const struct cert_Test05_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	if (strcmp(in->string, service_test04_out.string) != 0 ||
			!service_bump_test_num(state, 5)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test05_close_with_reply(call, &service_test05_out);
}

static void handle_test06(struct cert_Test06_service_call call, const struct cert_Test06_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	if (in->bool_ != service_test05_out.bool_ ||
			in->int_ != service_test05_out.int_ ||
			in->float_ != service_test05_out.float_ ||
			strcmp(in->string, service_test05_out.string) != 0 ||
			!service_bump_test_num(state, 6)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test06_close_with_reply(call, &service_test06_out);
}

static void handle_test07(struct cert_Test07_service_call call, const struct cert_Test07_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	if (in->struct_.bool_ != service_test06_out.struct_.bool_ ||
			in->struct_.int_ != service_test06_out.struct_.int_ ||
			in->struct_.float_ != service_test06_out.struct_.float_ ||
			strcmp(in->struct_.string, service_test06_out.struct_.string) != 0 ||
			!service_bump_test_num(state, 7)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test07_close_with_reply(call, &service_test07_out);
}

static void handle_test08(struct cert_Test08_service_call call, const struct cert_Test08_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	bool ok = in->map.len == service_test07_out.map.len;
	if (ok) {
		for (size_t i = 0; i < service_test07_out.map.len; i++) {
			const struct cert_Test07_out_map_entry want = service_test07_out.map.data[i];

			bool found = false;
			for (size_t j = 0; j < in->map.len; j++) {
				if (strcmp(in->map.data[j].key, want.key) == 0 &&
						strcmp(in->map.data[j].value, want.value) == 0) {
					found = true;
					break;
				}
			}
			if (!found) {
				ok = false;
				break;
			}
		}
	}
	if (!ok || !service_bump_test_num(state, 8)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	cert_Test08_close_with_reply(call, &service_test08_out);
}

static void handle_test09(struct cert_Test09_service_call call, const struct cert_Test09_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	bool ok = in->set.len == service_test08_out.set.len;
	if (ok) {
		for (size_t i = 0; i < service_test08_out.set.len; i++) {
			const char *want = service_test08_out.set.data[i].key;

			bool found = false;
			for (size_t j = 0; j < in->set.len; j++) {
				if (strcmp(in->set.data[j].key, want) == 0) {
					found = true;
					break;
				}
			}
			if (!found) {
				ok = false;
				break;
			}
		}
	}
	if (!ok || !service_bump_test_num(state, 9)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	// TODO: find a better way to do this
	json_object_put(service_test09_out.mytype.object);
	service_test09_out.mytype.object = json_object_new_int(42);

	cert_Test09_close_with_reply(call, &service_test09_out);
}

static void handle_test10(struct cert_Test10_service_call call, const struct cert_Test10_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	const struct cert_MyType *got = &in->mytype;
	const struct cert_MyType *want = &service_test09_out.mytype;
	bool ok =
		json_object_equal(got->object, want->object) &&
		got->enum_ == want->enum_ &&
		got->struct_.first == want->struct_.first &&
		strcmp(got->struct_.second, want->struct_.second) == 0 &&
		got->array.len == want->array.len &&
		got->dictionary.len == want->dictionary.len &&
		got->stringset.len == want->stringset.len &&
		got->nullable == NULL && want->nullable == NULL &&
		got->nullable_array_struct != NULL && got->nullable_array_struct->len == want->nullable_array_struct->len &&
		got->interface.foo != NULL && got->interface.foo->len == want->interface.foo->len &&
		got->interface.anon.foo == want->interface.anon.foo &&
		got->interface.anon.bar == want->interface.anon.bar;
	if (ok) {
		for (size_t i = 0; i < want->array.len; i++) {
			if (strcmp(got->array.data[i], want->array.data[i]) != 0) {
				ok = false;
			}
		}
		for (size_t i = 0; i < want->dictionary.len; i++) {
			const struct cert_MyType_dictionary_entry want_entry = want->dictionary.data[i];
			bool found = false;
			for (size_t j = 0; j < got->dictionary.len; j++) {
				const struct cert_MyType_dictionary_entry got_entry = got->dictionary.data[i];
				if (strcmp(got_entry.key, want_entry.key) == 0 &&
						strcmp(got_entry.value, want_entry.value) == 0) {
					found = true;
					break;
				}
			}
			if (!found) {
				ok = false;
			}
		}
		for (size_t i = 0; i < want->stringset.len; i++) {
			const char *want_key = want->stringset.data[i].key;
			bool found = false;
			for (size_t j = 0; j < got->stringset.len; j++) {
				if (strcmp(got->stringset.data[j].key, want_key) == 0) {
					found = true;
					break;
				}
			}
			if (!found) {
				ok = false;
			}
		}
		for (size_t i = 0; i < want->nullable_array_struct->len; i++) {
			const struct cert_MyType_nullable_array_struct_entry *want_entry =
				&want->nullable_array_struct->data[i];
			const struct cert_MyType_nullable_array_struct_entry *got_entry =
				&got->nullable_array_struct->data[i];
			if (want_entry->first != got_entry->first ||
					strcmp(want_entry->second, got_entry->second) != 0) {
				ok = false;
			}
		}
		for (size_t i = 0; i < want->interface.foo->len; i++) {
			const struct cert_Interface_foo_entry *want_entry = want->interface.foo->data[i];
			const struct cert_Interface_foo_entry *got_entry = got->interface.foo->data[i];
			if (want_entry == NULL) {
				if (got_entry != NULL) {
					ok = false;
				}
				break;
			}
			if (got_entry->len != want_entry->len) {
				ok = false;
				break;
			}
			for (size_t j = 0; j < want_entry->len; j++) {
				const struct cert_Interface_foo_entry_entry want_nested_entry = want_entry->data[j];
				bool found = false;
				for (size_t k = 0; k < got_entry->len; k++) {
					const struct cert_Interface_foo_entry_entry got_nested_entry = got_entry->data[k];
					if (strcmp(got_nested_entry.key, want_nested_entry.key) == 0 &&
							got_nested_entry.value == want_nested_entry.value) {
						found = true;
						break;
					}
				}
				if (!found) {
					ok = false;
				}
			}
		}
	}
	if (!ok || !service_bump_test_num(state, 10)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	if (!vali_service_call_is_more(call.base)) {
		vali_service_conn_disconnect(vali_service_call_get_conn(call.base));
		return;
	}

	for (size_t i = 0; i < service_test10_replies_len; i++) {
		struct cert_Test10_out out = { .string = service_test10_replies[i] };
		if (i < service_test10_replies_len - 1) {
			cert_Test10_reply(call, &out);
		} else {
			cert_Test10_close_with_reply(call, &out);
		}
	}
}

static void handle_test11(struct cert_Test11_service_call call, const struct cert_Test11_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	bool ok = in->last_more_replies.len == service_test10_replies_len;
	if (ok) {
		for (size_t i = 0; i < service_test10_replies_len; i++) {
			if (strcmp(in->last_more_replies.data[i], service_test10_replies[i]) != 0) {
				ok = false;
				break;
			}
		}
	}
	if (!ok || !service_bump_test_num(state, 11)) {
		cert_error_CertificationError_close_service_call(call.base, &(struct cert_error_CertificationError){0});
		return;
	}

	if (!vali_service_call_is_oneway(call.base)) {
		vali_service_conn_disconnect(vali_service_call_get_conn(call.base));
		return;
	}

	// TODO: introduce a new function to close without a reply
	cert_Test11_close_with_reply(call, NULL);
}

static void handle_end(struct cert_End_service_call call, const struct cert_End_in *in) {
	struct service_state *state = service_state_from_call(call.base);
	if (!service_check_client_id(state, in->client_id)) {
		cert_error_ClientIdError_close_service_call(call.base, NULL);
		return;
	}

	bool all_ok = state->prev_test_num == 11;
	free(state->client_id);
	state->client_id = NULL;
	state->prev_test_num = 0;

	cert_End_close_with_reply(call, &(struct cert_End_out){
		.all_ok = all_ok,
	});
}

static const struct cert_handler handler = {
	.Start = handle_start,
	.Test01 = handle_test01,
	.Test02 = handle_test02,
	.Test03 = handle_test03,
	.Test04 = handle_test04,
	.Test05 = handle_test05,
	.Test06 = handle_test06,
	.Test07 = handle_test07,
	.Test08 = handle_test08,
	.Test09 = handle_test09,
	.Test10 = handle_test10,
	.Test11 = handle_test11,
	.End = handle_end,
};

static struct vali_registry *service_register(struct vali_service *service, struct service_state *state) {
	const struct vali_registry_options registry_options = {
		.vendor = "emersion",
		.product = "Varlink Certification Service",
		.version = "1.0",
		.url = "https://gitlab.freedesktop.org/emersion/vali",
	};
	struct vali_registry *registry = vali_registry_create(&registry_options);
	if (registry == NULL) {
		return NULL;
	}

	vali_registry_add(registry, &cert_interface, cert_get_call_handler(&handler));
	vali_service_set_call_handler(service, vali_registry_get_call_handler(registry));
	vali_service_set_user_data(service, state);

	return registry;
}

static bool service_run_once_with_fd(int socket_fd) {
	struct vali_service *service = vali_service_create();
	if (service == NULL) {
		fprintf(stderr, "Failed to create service\n");
		return false;
	}

	struct service_state state = {0};
	struct vali_registry *registry = service_register(service, &state);
	if (registry == NULL) {
		fprintf(stderr, "Failed to create registry\n");
		return false;
	}

	struct vali_service_conn *service_conn = vali_service_create_conn(service, socket_fd);
	if (service_conn == NULL) {
		fprintf(stderr, "Failed to create service connection\n");
		return false;
	}

	while (1) {
		struct pollfd pollfds[] = {
			{
				.fd = vali_service_get_fd(service),
				.events = POLLIN,
			},
			{
				.fd = socket_fd,
				.events = 0,
			},
		};
		if (poll(pollfds, sizeof(pollfds) / sizeof(pollfds[0]), -1) < 0) {
			perror("poll");
			return false;
		}

		if (pollfds[0].revents & POLLIN) {
			if (!vali_service_dispatch(service)) {
				fprintf(stderr, "Failed to dispatch service\n");
				return false;
			}
		}

		if (pollfds[1].revents & (POLLHUP | POLLERR)) {
			break;
		}
	}

	vali_service_destroy(service);
	vali_registry_destroy(registry);

	return true;
}

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

	const char *subcmd = argv[1];
	if (strcmp(subcmd, "client") == 0) {
		if (argc != 3) {
			fprintf(stderr, "%s", usage);
			return 1;
		}
		const char *path = argv[2];

		struct vali_client *client = vali_client_connect_unix(path);
		if (client == NULL) {
			fprintf(stderr, "Failed to connect to %s\n", path);
			return 1;
		}

		if (!run_client(client)) {
			return 1;
		}
	} else if (strcmp(subcmd, "service") == 0) {
		if (argc != 3) {
			fprintf(stderr, "%s", usage);
			return 1;
		}
		const char *path = argv[2];

		struct vali_service *service = vali_service_create();
		if (service == NULL) {
			fprintf(stderr, "Failed to create service\n");
			return 1;
		}

		struct service_state state = {0};
		struct vali_registry *registry = service_register(service, &state);
		if (registry == NULL) {
			fprintf(stderr, "Failed to create registry\n");
			return 1;
		}

		unlink(path);
		if (!vali_service_listen_unix(service, path)) {
			fprintf(stderr, "Failed to start service listener\n");
			return 1;
		}

		fprintf(stderr, "Listening on %s\n", path);
		while (vali_service_dispatch(service));
		fprintf(stderr, "Failed to dispatch service\n");

		vali_service_destroy(service);
		vali_registry_destroy(registry);
		return 1;
	} else if (strcmp(subcmd, "self-test") == 0) {
		if (argc != 2) {
			fprintf(stderr, "%s", usage);
			return 1;
		}

		int sockets[2] = { -1, -1 };
		int ret = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets);
		assert(ret == 0);

		pid_t pid = fork();
		assert(pid >= 0);
		if (pid == 0) {
			close(sockets[1]);
			if (!service_run_once_with_fd(sockets[0])) {
				_exit(1);
			}
			_exit(0);
		}

		close(sockets[0]);

		struct vali_client *client = vali_client_connect_fd(sockets[1]);
		if (client == NULL) {
			fprintf(stderr, "Failed to create client\n");
			return 1;
		}

		if (!run_client(client)) {
			return 1;
		}

		int stat = 0;
		if (waitpid(pid, &stat, 0) < 0) {
			perror("waitpid");
			return 1;
		} else if (!WIFEXITED(stat) || WEXITSTATUS(stat) != 0) {
			fprintf(stderr, "Service process failed\n");
			return 1;
		}
	} else {
		fprintf(stderr, "%s", usage);
		return 1;
	}
	return 0;
}
