#include <assert.h>
#include <stddef.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>

#include <vali.h>

#include "test-gen.h"

static bool running = true;

static void handle_echo(struct test_Echo_service_call call, const struct test_Echo_in *in) {
	const struct test_Echo_out out = {
		.s = in->s,
		.a = {
			.data = in->a.data,
			.len = in->a.len,
		},
		.m = {
			.data = (struct test_Echo_out_m_entry *)in->m.data,
			.len = in->m.len,
		},
	};
	test_Echo_close_with_reply(call, &out);
}

static void handle_count_until(struct test_CountUntil_service_call call, const struct test_CountUntil_in *in) {
	assert(vali_service_call_is_more(call.base));

	for (int i = 0; i < in->n; i++) {
		test_CountUntil_reply(call, &(struct test_CountUntil_out){ .n = i });
	}

	test_CountUntil_close_with_reply(call, &(struct test_CountUntil_out){ .n = in->n });
}

static void handle_stop(struct test_Stop_service_call call, const struct test_Stop_in *in) {
	running = false;
	test_Stop_close_with_reply(call, NULL);
}

static const struct test_handler test_handler = {
	.Echo = handle_echo,
	.CountUntil = handle_count_until,
	.Stop = handle_stop,
};

static void run_service(int fd) {
	struct vali_service *service = vali_service_create();

	const struct vali_registry_options registry_options = {
		.vendor = "FooCorp",
		.product = "example",
		.version = "1.0",
		.url = "https://example.org",
	};
	struct vali_registry *registry = vali_registry_create(&registry_options);
	vali_registry_add(registry, &test_interface, test_get_call_handler(&test_handler));
	vali_service_set_call_handler(service, vali_registry_get_call_handler(registry));

	struct vali_service_conn *service_conn = vali_service_create_conn(service, fd);
	assert(service_conn != NULL);

	while (running && vali_service_dispatch(service));
	vali_service_dispatch(service); // flush reply

	vali_service_destroy(service);
	vali_registry_destroy(registry);
}

static void test_echo(struct vali_client *client) {
	const struct test_Echo_in in = {
		.s = {
			.foo = 42,
			.bar = "bar",
			.baz = &(enum test_Enum){test_Enum_b},
		},
		.a = {
			.data = (bool[]){4, 2},
			.len = 2,
		},
		.m = {
			.data = (struct test_Echo_in_m_entry[]){
				{ .key = "one", .value = 1 },
				{ .key = "two", .value = 2 },
				{ .key = "three", .value = 3 },
			},
			.len = 3,
		},
	};
	struct test_Echo_out out = {0};
	bool ok = test_Echo(client, &in, &out, NULL);
	assert(ok);
	assert(out.s.foo == in.s.foo);
	assert(strcmp(out.s.bar, in.s.bar) == 0);
	assert(*out.s.baz == *in.s.baz);
	assert(out.a.len == in.a.len);
	for (size_t i = 0; i < out.a.len; i++) {
		assert(out.a.data[i] == in.a.data[i]);
	}
	assert(out.m.len == in.m.len);
	for (size_t i = 0; i < out.m.len; i++) {
		assert(strcmp(out.m.data[i].key, in.m.data[i].key) == 0);
		assert(out.m.data[i].value == in.m.data[i].value);
	}
	test_Echo_out_finish(&out);
}

static void test_count_until(struct vali_client *client) {
	const struct test_CountUntil_in in = {
		.n = 10,
	};
	struct test_CountUntil_client_call call = test_CountUntil_more(client, &in);
	assert(call.base != NULL);

	for (int i = 0; i <= in.n; i++) {
		assert(!vali_client_call_is_done(call.base));
		struct test_CountUntil_out out = {0};
		bool ok = test_CountUntil_client_call_wait(call, &out, NULL);
		assert(ok);
		assert(out.n == i);
	}

	assert(vali_client_call_is_done(call.base));
	vali_client_call_destroy(call.base);
}

int main(int argc, char *argv[]) {
#ifdef NDEBUG
	fprintf(stderr, "NDEBUG must be disabled for tests\n");
	return 1;
#endif

	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]);
		run_service(sockets[0]);
		_exit(0);
	}

	close(sockets[0]);

	struct vali_client *client = vali_client_connect_fd(sockets[1]);
	assert(client != NULL);

	test_echo(client);
	test_count_until(client);

	bool ok = test_Stop(client, NULL, NULL, NULL);
	assert(ok);

	vali_client_destroy(client);

	int stat = 0;
	ret = waitpid(pid, &stat, 0);
	assert(ret >= 0);
	assert(WIFEXITED(stat) && WEXITSTATUS(stat) == 0);

	return 0;
}
