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

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

#include "org.varlink.service.h"

#if __STDC_VERSION__ >= 202311L
#define MAYBE_UNUSED [[maybe_unused]]
#elif defined(__GNUC__)
#define MAYBE_UNUSED __attribute__((__unused__))
#else
#define MAYBE_UNUSED
#endif

#define HIDDEN
#if defined(__has_attribute)
#if __has_attribute(visibility)
#undef HIDDEN
#define HIDDEN __attribute__((visibility("hidden")))
#endif
#endif

MAYBE_UNUSED
static struct json_object *GetInfo_in_encode(const struct org_varlink_service_GetInfo_in *v) {
	struct json_object *raw = json_object_new_object();
	return raw;
}

MAYBE_UNUSED
static bool GetInfo_in_decode(struct org_varlink_service_GetInfo_in *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}


	return true;
}

MAYBE_UNUSED
static void org_varlink_service_GetInfo_in_finish(struct org_varlink_service_GetInfo_in *v) {
}

MAYBE_UNUSED
static struct json_object *GetInfo_out_encode(const struct org_varlink_service_GetInfo_out *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_vendor = json_object_new_string(v->vendor);
	json_object_object_add(raw, "vendor", raw_vendor);
	struct json_object *raw_product = json_object_new_string(v->product);
	json_object_object_add(raw, "product", raw_product);
	struct json_object *raw_version = json_object_new_string(v->version);
	json_object_object_add(raw, "version", raw_version);
	struct json_object *raw_url = json_object_new_string(v->url);
	json_object_object_add(raw, "url", raw_url);
	struct json_object *raw_interfaces = json_object_new_array();
	for (size_t i = 0; i < v->interfaces.len; i++) {
		char *item = v->interfaces.data[i];
		struct json_object *raw_item = json_object_new_string(item);
		json_object_array_add(raw_interfaces, raw_item);
	}
	json_object_object_add(raw, "interfaces", raw_interfaces);
	return raw;
}

MAYBE_UNUSED
static bool GetInfo_out_decode(struct org_varlink_service_GetInfo_out *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_vendor = json_object_object_get(obj, "vendor");
	if (raw_vendor == NULL) {
		return false;
	}
	if (json_object_get_type(raw_vendor) != json_type_string) {
		return false;
	}
	out->vendor = strdup(json_object_get_string(raw_vendor));

	struct json_object *raw_product = json_object_object_get(obj, "product");
	if (raw_product == NULL) {
		return false;
	}
	if (json_object_get_type(raw_product) != json_type_string) {
		return false;
	}
	out->product = strdup(json_object_get_string(raw_product));

	struct json_object *raw_version = json_object_object_get(obj, "version");
	if (raw_version == NULL) {
		return false;
	}
	if (json_object_get_type(raw_version) != json_type_string) {
		return false;
	}
	out->version = strdup(json_object_get_string(raw_version));

	struct json_object *raw_url = json_object_object_get(obj, "url");
	if (raw_url == NULL) {
		return false;
	}
	if (json_object_get_type(raw_url) != json_type_string) {
		return false;
	}
	out->url = strdup(json_object_get_string(raw_url));

	struct json_object *raw_interfaces = json_object_object_get(obj, "interfaces");
	if (raw_interfaces == NULL) {
		return false;
	}
	if (json_object_get_type(raw_interfaces) != json_type_array) {
		return false;
	}
	size_t raw_interfaces_len = json_object_array_length(raw_interfaces);
	out->interfaces.data = calloc(raw_interfaces_len, sizeof(out->interfaces.data[0]));
	if (out->interfaces.data == NULL) {
		return false;
	}
	for (size_t i = 0; i < raw_interfaces_len; i++) {
		struct json_object *item_obj = json_object_array_get_idx(raw_interfaces, i);
		char *item = {0};
		if (item_obj == NULL) {
			return false;
		}
		if (json_object_get_type(item_obj) != json_type_string) {
			return false;
		}
		item = strdup(json_object_get_string(item_obj));
		out->interfaces.data[i] = item;
		out->interfaces.len++;
	}

	return true;
}

HIDDEN
void org_varlink_service_GetInfo_out_finish(struct org_varlink_service_GetInfo_out *v) {
	free(v->vendor);
	free(v->product);
	free(v->version);
	free(v->url);
	for (size_t i = 0; i < v->interfaces.len; i++) {
		MAYBE_UNUSED char *entry = v->interfaces.data[i];
		free(entry);
	}
	free(v->interfaces.data);
}

HIDDEN
bool org_varlink_service_GetInfo(struct vali_client *c, const struct org_varlink_service_GetInfo_in *in, struct org_varlink_service_GetInfo_out *out, struct vali_error *err) {
	struct json_object *raw_in = GetInfo_in_encode(in);
	struct json_object *raw_out = NULL;
	if (!vali_client_call(c, "org.varlink.service.GetInfo", raw_in, &raw_out, err)) {
		return false;
	}
	bool ok = true;
	if (out != NULL) {
		*out = (struct org_varlink_service_GetInfo_out){0};
		ok = GetInfo_out_decode(out, raw_out);
	}
	json_object_put(raw_out);
	return ok;
}

HIDDEN
struct org_varlink_service_GetInfo_client_call org_varlink_service_GetInfo_more(struct vali_client *c, const struct org_varlink_service_GetInfo_in *in) {
	struct json_object *raw_in = GetInfo_in_encode(in);
	struct vali_client_call *call = vali_client_call_more(c, "org.varlink.service.GetInfo", raw_in);
	return (struct org_varlink_service_GetInfo_client_call){ .base = call };
}

HIDDEN
bool org_varlink_service_GetInfo_oneway(struct vali_client *c, const struct org_varlink_service_GetInfo_in *in) {
	struct json_object *raw_in = GetInfo_in_encode(in);
	return vali_client_call_oneway(c, "org.varlink.service.GetInfo", raw_in);
}

HIDDEN
bool org_varlink_service_GetInfo_client_call_wait(struct org_varlink_service_GetInfo_client_call call, struct org_varlink_service_GetInfo_out *out, struct vali_error *err) {
	struct json_object *raw_out = NULL;
	if (!vali_client_call_wait(call.base, &raw_out, err)) {
		return false;
	}
	bool ok = true;
	if (out != NULL) {
		*out = (struct org_varlink_service_GetInfo_out){0};
		ok = GetInfo_out_decode(out, raw_out);
	}
	json_object_put(raw_out);
	return ok;
}
HIDDEN
void org_varlink_service_GetInfo_close_with_reply(struct org_varlink_service_GetInfo_service_call call, const struct org_varlink_service_GetInfo_out *params) {
	struct json_object *raw = GetInfo_out_encode(params);
	vali_service_call_close_with_reply(call.base, raw);
}
HIDDEN
void org_varlink_service_GetInfo_reply(struct org_varlink_service_GetInfo_service_call call, const struct org_varlink_service_GetInfo_out *params) {
	struct json_object *raw = GetInfo_out_encode(params);
	vali_service_call_reply(call.base, raw);
}

MAYBE_UNUSED
static struct json_object *GetInterfaceDescription_in_encode(const struct org_varlink_service_GetInterfaceDescription_in *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_interface = json_object_new_string(v->interface);
	json_object_object_add(raw, "interface", raw_interface);
	return raw;
}

MAYBE_UNUSED
static bool GetInterfaceDescription_in_decode(struct org_varlink_service_GetInterfaceDescription_in *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_interface = json_object_object_get(obj, "interface");
	if (raw_interface == NULL) {
		return false;
	}
	if (json_object_get_type(raw_interface) != json_type_string) {
		return false;
	}
	out->interface = strdup(json_object_get_string(raw_interface));

	return true;
}

MAYBE_UNUSED
static void org_varlink_service_GetInterfaceDescription_in_finish(struct org_varlink_service_GetInterfaceDescription_in *v) {
	free(v->interface);
}

MAYBE_UNUSED
static struct json_object *GetInterfaceDescription_out_encode(const struct org_varlink_service_GetInterfaceDescription_out *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_description = json_object_new_string(v->description);
	json_object_object_add(raw, "description", raw_description);
	return raw;
}

MAYBE_UNUSED
static bool GetInterfaceDescription_out_decode(struct org_varlink_service_GetInterfaceDescription_out *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_description = json_object_object_get(obj, "description");
	if (raw_description == NULL) {
		return false;
	}
	if (json_object_get_type(raw_description) != json_type_string) {
		return false;
	}
	out->description = strdup(json_object_get_string(raw_description));

	return true;
}

HIDDEN
void org_varlink_service_GetInterfaceDescription_out_finish(struct org_varlink_service_GetInterfaceDescription_out *v) {
	free(v->description);
}

HIDDEN
bool org_varlink_service_GetInterfaceDescription(struct vali_client *c, const struct org_varlink_service_GetInterfaceDescription_in *in, struct org_varlink_service_GetInterfaceDescription_out *out, struct vali_error *err) {
	struct json_object *raw_in = GetInterfaceDescription_in_encode(in);
	struct json_object *raw_out = NULL;
	if (!vali_client_call(c, "org.varlink.service.GetInterfaceDescription", raw_in, &raw_out, err)) {
		return false;
	}
	bool ok = true;
	if (out != NULL) {
		*out = (struct org_varlink_service_GetInterfaceDescription_out){0};
		ok = GetInterfaceDescription_out_decode(out, raw_out);
	}
	json_object_put(raw_out);
	return ok;
}

HIDDEN
struct org_varlink_service_GetInterfaceDescription_client_call org_varlink_service_GetInterfaceDescription_more(struct vali_client *c, const struct org_varlink_service_GetInterfaceDescription_in *in) {
	struct json_object *raw_in = GetInterfaceDescription_in_encode(in);
	struct vali_client_call *call = vali_client_call_more(c, "org.varlink.service.GetInterfaceDescription", raw_in);
	return (struct org_varlink_service_GetInterfaceDescription_client_call){ .base = call };
}

HIDDEN
bool org_varlink_service_GetInterfaceDescription_oneway(struct vali_client *c, const struct org_varlink_service_GetInterfaceDescription_in *in) {
	struct json_object *raw_in = GetInterfaceDescription_in_encode(in);
	return vali_client_call_oneway(c, "org.varlink.service.GetInterfaceDescription", raw_in);
}

HIDDEN
bool org_varlink_service_GetInterfaceDescription_client_call_wait(struct org_varlink_service_GetInterfaceDescription_client_call call, struct org_varlink_service_GetInterfaceDescription_out *out, struct vali_error *err) {
	struct json_object *raw_out = NULL;
	if (!vali_client_call_wait(call.base, &raw_out, err)) {
		return false;
	}
	bool ok = true;
	if (out != NULL) {
		*out = (struct org_varlink_service_GetInterfaceDescription_out){0};
		ok = GetInterfaceDescription_out_decode(out, raw_out);
	}
	json_object_put(raw_out);
	return ok;
}
HIDDEN
void org_varlink_service_GetInterfaceDescription_close_with_reply(struct org_varlink_service_GetInterfaceDescription_service_call call, const struct org_varlink_service_GetInterfaceDescription_out *params) {
	struct json_object *raw = GetInterfaceDescription_out_encode(params);
	vali_service_call_close_with_reply(call.base, raw);
}
HIDDEN
void org_varlink_service_GetInterfaceDescription_reply(struct org_varlink_service_GetInterfaceDescription_service_call call, const struct org_varlink_service_GetInterfaceDescription_out *params) {
	struct json_object *raw = GetInterfaceDescription_out_encode(params);
	vali_service_call_reply(call.base, raw);
}

MAYBE_UNUSED
static struct json_object *error_InterfaceNotFound_encode(const struct org_varlink_service_error_InterfaceNotFound *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_interface = json_object_new_string(v->interface);
	json_object_object_add(raw, "interface", raw_interface);
	return raw;
}
MAYBE_UNUSED
static bool error_InterfaceNotFound_decode(struct org_varlink_service_error_InterfaceNotFound *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_interface = json_object_object_get(obj, "interface");
	if (raw_interface == NULL) {
		return false;
	}
	if (json_object_get_type(raw_interface) != json_type_string) {
		return false;
	}
	out->interface = strdup(json_object_get_string(raw_interface));

	return true;
}
bool org_varlink_service_error_InterfaceNotFound_from(struct org_varlink_service_error_InterfaceNotFound *out, const struct vali_error *err) {
	if (err->name == NULL || strcmp(err->name, "org.varlink.service.InterfaceNotFound") != 0) {
		return false;
	}
	return error_InterfaceNotFound_decode(out, err->parameters);
}
void org_varlink_service_error_InterfaceNotFound_close_service_call(struct vali_service_call *call, const struct org_varlink_service_error_InterfaceNotFound *params) {
	struct json_object *obj = error_InterfaceNotFound_encode(params);
	vali_service_call_close_with_error(call, "org.varlink.service.InterfaceNotFound", obj);
}

MAYBE_UNUSED
static struct json_object *error_MethodNotFound_encode(const struct org_varlink_service_error_MethodNotFound *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_method = json_object_new_string(v->method);
	json_object_object_add(raw, "method", raw_method);
	return raw;
}
MAYBE_UNUSED
static bool error_MethodNotFound_decode(struct org_varlink_service_error_MethodNotFound *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_method = json_object_object_get(obj, "method");
	if (raw_method == NULL) {
		return false;
	}
	if (json_object_get_type(raw_method) != json_type_string) {
		return false;
	}
	out->method = strdup(json_object_get_string(raw_method));

	return true;
}
bool org_varlink_service_error_MethodNotFound_from(struct org_varlink_service_error_MethodNotFound *out, const struct vali_error *err) {
	if (err->name == NULL || strcmp(err->name, "org.varlink.service.MethodNotFound") != 0) {
		return false;
	}
	return error_MethodNotFound_decode(out, err->parameters);
}
void org_varlink_service_error_MethodNotFound_close_service_call(struct vali_service_call *call, const struct org_varlink_service_error_MethodNotFound *params) {
	struct json_object *obj = error_MethodNotFound_encode(params);
	vali_service_call_close_with_error(call, "org.varlink.service.MethodNotFound", obj);
}

MAYBE_UNUSED
static struct json_object *error_MethodNotImplemented_encode(const struct org_varlink_service_error_MethodNotImplemented *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_method = json_object_new_string(v->method);
	json_object_object_add(raw, "method", raw_method);
	return raw;
}
MAYBE_UNUSED
static bool error_MethodNotImplemented_decode(struct org_varlink_service_error_MethodNotImplemented *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_method = json_object_object_get(obj, "method");
	if (raw_method == NULL) {
		return false;
	}
	if (json_object_get_type(raw_method) != json_type_string) {
		return false;
	}
	out->method = strdup(json_object_get_string(raw_method));

	return true;
}
bool org_varlink_service_error_MethodNotImplemented_from(struct org_varlink_service_error_MethodNotImplemented *out, const struct vali_error *err) {
	if (err->name == NULL || strcmp(err->name, "org.varlink.service.MethodNotImplemented") != 0) {
		return false;
	}
	return error_MethodNotImplemented_decode(out, err->parameters);
}
void org_varlink_service_error_MethodNotImplemented_close_service_call(struct vali_service_call *call, const struct org_varlink_service_error_MethodNotImplemented *params) {
	struct json_object *obj = error_MethodNotImplemented_encode(params);
	vali_service_call_close_with_error(call, "org.varlink.service.MethodNotImplemented", obj);
}

MAYBE_UNUSED
static struct json_object *error_InvalidParameter_encode(const struct org_varlink_service_error_InvalidParameter *v) {
	struct json_object *raw = json_object_new_object();
	struct json_object *raw_parameter = json_object_new_string(v->parameter);
	json_object_object_add(raw, "parameter", raw_parameter);
	return raw;
}
MAYBE_UNUSED
static bool error_InvalidParameter_decode(struct org_varlink_service_error_InvalidParameter *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}

	struct json_object *raw_parameter = json_object_object_get(obj, "parameter");
	if (raw_parameter == NULL) {
		return false;
	}
	if (json_object_get_type(raw_parameter) != json_type_string) {
		return false;
	}
	out->parameter = strdup(json_object_get_string(raw_parameter));

	return true;
}
bool org_varlink_service_error_InvalidParameter_from(struct org_varlink_service_error_InvalidParameter *out, const struct vali_error *err) {
	if (err->name == NULL || strcmp(err->name, "org.varlink.service.InvalidParameter") != 0) {
		return false;
	}
	return error_InvalidParameter_decode(out, err->parameters);
}
void org_varlink_service_error_InvalidParameter_close_service_call(struct vali_service_call *call, const struct org_varlink_service_error_InvalidParameter *params) {
	struct json_object *obj = error_InvalidParameter_encode(params);
	vali_service_call_close_with_error(call, "org.varlink.service.InvalidParameter", obj);
}

MAYBE_UNUSED
static struct json_object *error_PermissionDenied_encode(const struct org_varlink_service_error_PermissionDenied *v) {
	struct json_object *raw = json_object_new_object();
	return raw;
}
MAYBE_UNUSED
static bool error_PermissionDenied_decode(struct org_varlink_service_error_PermissionDenied *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}


	return true;
}
bool org_varlink_service_error_PermissionDenied_from(struct org_varlink_service_error_PermissionDenied *out, const struct vali_error *err) {
	if (err->name == NULL || strcmp(err->name, "org.varlink.service.PermissionDenied") != 0) {
		return false;
	}
	return error_PermissionDenied_decode(out, err->parameters);
}
void org_varlink_service_error_PermissionDenied_close_service_call(struct vali_service_call *call, const struct org_varlink_service_error_PermissionDenied *params) {
	struct json_object *obj = error_PermissionDenied_encode(params);
	vali_service_call_close_with_error(call, "org.varlink.service.PermissionDenied", obj);
}

MAYBE_UNUSED
static struct json_object *error_ExpectedMore_encode(const struct org_varlink_service_error_ExpectedMore *v) {
	struct json_object *raw = json_object_new_object();
	return raw;
}
MAYBE_UNUSED
static bool error_ExpectedMore_decode(struct org_varlink_service_error_ExpectedMore *out, struct json_object *obj) {
	if (json_object_get_type(obj) != json_type_object && obj != NULL) {
		return false;
	}


	return true;
}
bool org_varlink_service_error_ExpectedMore_from(struct org_varlink_service_error_ExpectedMore *out, const struct vali_error *err) {
	if (err->name == NULL || strcmp(err->name, "org.varlink.service.ExpectedMore") != 0) {
		return false;
	}
	return error_ExpectedMore_decode(out, err->parameters);
}
void org_varlink_service_error_ExpectedMore_close_service_call(struct vali_service_call *call, const struct org_varlink_service_error_ExpectedMore *params) {
	struct json_object *obj = error_ExpectedMore_encode(params);
	vali_service_call_close_with_error(call, "org.varlink.service.ExpectedMore", obj);
}

MAYBE_UNUSED
static void fail_method_not_implemented(struct vali_service_call *call, const char *method) {
	struct json_object *params = json_object_new_object();
	json_object_object_add(params, "method", json_object_new_string(method));
	vali_service_call_close_with_error(call, "org.varlink.service.MethodNotImplemented", params);
}

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

static void handle_call(struct vali_service_call *call, void *user_data) {
	const struct org_varlink_service_handler *handler = user_data;
	const char *method = vali_service_call_get_method(call);
	struct json_object *raw_in = vali_service_call_get_parameters(call);
	if (strcmp(method, "org.varlink.service.GetInfo") == 0) {
		if (handler->GetInfo == NULL) {
			fail_method_not_implemented(call, method);
			return;
		}
		struct org_varlink_service_GetInfo_in in = {0};
		if (!GetInfo_in_decode(&in, raw_in)) {
			fail_invalid_parameter(call, NULL);
			return;
		}
		handler->GetInfo((struct org_varlink_service_GetInfo_service_call){call}, &in);
		org_varlink_service_GetInfo_in_finish(&in);
		return;
	}
	if (strcmp(method, "org.varlink.service.GetInterfaceDescription") == 0) {
		if (handler->GetInterfaceDescription == NULL) {
			fail_method_not_implemented(call, method);
			return;
		}
		struct org_varlink_service_GetInterfaceDescription_in in = {0};
		if (!GetInterfaceDescription_in_decode(&in, raw_in)) {
			fail_invalid_parameter(call, NULL);
			return;
		}
		handler->GetInterfaceDescription((struct org_varlink_service_GetInterfaceDescription_service_call){call}, &in);
		org_varlink_service_GetInterfaceDescription_in_finish(&in);
		return;
	}
	struct json_object *params = json_object_new_object();
	json_object_object_add(params, "method", json_object_new_string(method));
	vali_service_call_close_with_error(call, "org.varlink.service.MethodNotFound", params);
}

HIDDEN
const struct vali_registry_interface org_varlink_service_interface = {
	.name = "org.varlink.service",
	.definition =
		"# The Varlink Service Interface is provided by every varlink service. It\n"
		"# describes the service and the interfaces it implements.\n"
		"interface org.varlink.service\n"
		"\n"
		"# Get a list of all the interfaces a service provides and information\n"
		"# about the implementation.\n"
		"method GetInfo() -> (\n"
		"  vendor: string,\n"
		"  product: string,\n"
		"  version: string,\n"
		"  url: string,\n"
		"  interfaces: []string\n"
		")\n"
		"\n"
		"# Get the description of an interface that is implemented by this service.\n"
		"method GetInterfaceDescription(interface: string) -> (description: string)\n"
		"\n"
		"# The requested interface was not found.\n"
		"error InterfaceNotFound (interface: string)\n"
		"\n"
		"# The requested method was not found\n"
		"error MethodNotFound (method: string)\n"
		"\n"
		"# The interface defines the requested method, but the service does not\n"
		"# implement it.\n"
		"error MethodNotImplemented (method: string)\n"
		"\n"
		"# One of the passed parameters is invalid.\n"
		"error InvalidParameter (parameter: string)\n"
		"\n"
		"# Client is denied access\n"
		"error PermissionDenied ()\n"
		"\n"
		"# Method is expected to be called with 'more' set to true, but wasn't\n"
		"error ExpectedMore ()\n"
		"",
};

HIDDEN
struct vali_service_call_handler org_varlink_service_get_call_handler(const struct org_varlink_service_handler *handler) {
	return (struct vali_service_call_handler){
		.func = handle_call,
		.user_data = (void *)handler,
	};
}

