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

#include <json_object.h>

#include "util.h"
#include "vali.h"

#include "org.varlink.service.h"

struct vali_registry {
	char *vendor;
	char *product;
	char *version;
	char *url;
	struct array interfaces; // struct vali_registry_entry
};

struct vali_registry_entry {
	const struct vali_registry_interface *interface;
	struct vali_service_call_handler call_handler;
};

static const struct vali_registry_entry *registry_get(struct vali_registry *registry, const char *iface_name, size_t iface_name_len) {
	const struct vali_registry_entry *interfaces = registry->interfaces.data;
	size_t interfaces_len = registry->interfaces.size / sizeof(interfaces[0]);
	for (size_t i = 0; i < interfaces_len; i++) {
		const struct vali_registry_entry *iface = &interfaces[i];
		if (strncmp(iface->interface->name, iface_name, iface_name_len) == 0) {
			return iface;
		}
	}
	return NULL;
}

static void handle_GetInfo(struct org_varlink_service_GetInfo_service_call call, const struct org_varlink_service_GetInfo_in *in) {
	struct vali_registry *registry = vali_service_call_get_user_data(call.base);

	struct org_varlink_service_GetInfo_out out = {
		.vendor = registry->vendor,
		.product = registry->product,
		.version = registry->version,
		.url = registry->url,
	};

	const struct vali_registry_entry *interfaces = registry->interfaces.data;
	size_t interfaces_len = registry->interfaces.size / sizeof(interfaces[0]);
	out.interfaces.data = calloc(interfaces_len, sizeof(out.interfaces.data[0]));
	if (out.interfaces.data == NULL) {
		vali_service_conn_disconnect(vali_service_call_get_conn(call.base));
		return;
	}

	for (size_t i = 0; i < interfaces_len; i++) {
		out.interfaces.data[i] = (char *)interfaces[i].interface->name;
		out.interfaces.len++;
	}

	org_varlink_service_GetInfo_close_with_reply(call, &out);
	free(out.interfaces.data);
}

static void handle_GetInterfaceDescription(struct org_varlink_service_GetInterfaceDescription_service_call call, const struct org_varlink_service_GetInterfaceDescription_in *in) {
	struct vali_registry *registry = vali_service_call_get_user_data(call.base);

	const struct vali_registry_entry *iface = registry_get(registry, in->interface, strlen(in->interface));
	if (iface == NULL) {
		vali_service_conn_disconnect(vali_service_call_get_conn(call.base));
		return; // TODO: send proper error
	}

	struct org_varlink_service_GetInterfaceDescription_out out = {
		.description = (char *)iface->interface->definition,
	};

	org_varlink_service_GetInterfaceDescription_close_with_reply(call, &out);
}

static const struct org_varlink_service_handler org_varlink_service_handler = {
	.GetInfo = handle_GetInfo,
	.GetInterfaceDescription = handle_GetInterfaceDescription,
};

static void org_varlink_service_handle_call(struct vali_service_call *call, void *user_data) {
	struct vali_registry *registry = user_data;

	vali_service_call_set_user_data(call, registry);

	struct vali_service_call_handler call_handler = org_varlink_service_get_call_handler(&org_varlink_service_handler);
	call_handler.func(call, call_handler.user_data);
}

struct vali_registry *vali_registry_create(const struct vali_registry_options *options) {
	struct vali_registry *registry = calloc(1, sizeof(*registry));
	if (registry == NULL) {
		return NULL;
	}

	registry->vendor = strdup(options->vendor);
	registry->product = strdup(options->product);
	registry->version = strdup(options->version);
	registry->url = strdup(options->url);
	if (registry->vendor == NULL || registry->product == NULL || registry->version == NULL || registry->url == NULL) {
		vali_registry_destroy(registry);
		return NULL;
	}

	const struct vali_service_call_handler call_handler = {
		.func = org_varlink_service_handle_call,
		.user_data = registry,
	};
	if (!vali_registry_add(registry, &org_varlink_service_interface, call_handler)) {
		vali_registry_destroy(registry);
		return NULL;
	}

	return registry;
}

void vali_registry_destroy(struct vali_registry *registry) {
	array_finish(&registry->interfaces);
	free(registry->vendor);
	free(registry->product);
	free(registry->version);
	free(registry->url);
	free(registry);
}

static void registry_handle_call(struct vali_service_call *call, void *user_data) {
	struct vali_registry *registry = user_data;

	const char *method = vali_service_call_get_method(call);
	const char *last_dot = strrchr(method, '.');
	if (last_dot == NULL) {
		vali_service_conn_disconnect(vali_service_call_get_conn(call));
		return; // TODO: send proper error
	}
	size_t iface_name_len = (size_t)(last_dot - method);

	const struct vali_registry_entry *iface = registry_get(registry, method, iface_name_len);
	if (iface != NULL) {
		iface->call_handler.func(call, iface->call_handler.user_data);
		return;
	}

	char *iface_name = strndup(method, iface_name_len);
	if (iface_name == NULL) {
		vali_service_conn_disconnect(vali_service_call_get_conn(call));
		return;
	}

	const struct org_varlink_service_error_InterfaceNotFound params = {
		.interface = iface_name,
	};
	org_varlink_service_error_InterfaceNotFound_close_service_call(call, &params);
	free(iface_name);
}

struct vali_service_call_handler vali_registry_get_call_handler(struct vali_registry *registry) {
	return (struct vali_service_call_handler){
		.func = registry_handle_call,
		.user_data = registry,
	};
}

bool vali_registry_add(struct vali_registry *registry, const struct vali_registry_interface *iface, struct vali_service_call_handler call_handler) {
	if (registry_get(registry, iface->name, strlen(iface->name))) {
		abort();
	}

	struct vali_registry_entry *dst = array_add(&registry->interfaces, sizeof(*dst));
	if (dst == NULL) {
		return false;
	}

	*dst = (struct vali_registry_entry){
		.interface = iface,
		.call_handler = call_handler,
	};
	return true;
}
