#include <fcntl.h>
#include <limits.h>
#include <stdio.h>
#include <string.h>
#include <unistd.h>

#include <vali.h>

#include "config.h"
#include "kanshi.h"
#include "ipc.h"
#include "ipc-gen.h"

static struct kanshi_state *state_from_call(struct vali_service_call *call) {
	struct vali_service *service = vali_service_call_get_service(call);
	return vali_service_get_user_data(service);
}

static void apply_profile_done(void *data, bool success) {
	struct vali_service_call *call = data;
	if (success) {
		vali_service_call_close_with_reply(call, NULL);
	} else {
		ipc_error_ProfileNotMatched_close_service_call(call, NULL);
	}
}

static void handle_status(struct ipc_Status_service_call call, const struct ipc_Status_in *in) {
	struct kanshi_state *state = state_from_call(call.base);
	ipc_Status_close_with_reply(call, &(struct ipc_Status_out){
		.current_profile = state->current_profile ? state->current_profile->name : NULL,
		.pending_profile = state->pending_profile ? state->pending_profile->name : NULL,
	});
}

static void handle_reload(struct ipc_Reload_service_call call, const struct ipc_Reload_in *in) {
	struct kanshi_state *state = state_from_call(call.base);
	if (!kanshi_reload_config(state, apply_profile_done, call.base)) {
		ipc_error_ProfileNotMatched_close_service_call(call.base, NULL);
	}
}

static void handle_switch(struct ipc_Switch_service_call call, const struct ipc_Switch_in *in) {
	struct kanshi_state *state = state_from_call(call.base);

	struct kanshi_profile *profile;
	bool found = false;
	bool matched = false;
	wl_list_for_each(profile, &state->config->profiles, link) {
		if (strcmp(profile->name, in->profile) != 0) {
			continue;
		}

		found = true;
		if (kanshi_switch(state, profile, apply_profile_done, call.base)) {
			matched = true;
			break;
		}
	}
	if (!found) {
		ipc_error_ProfileNotFound_close_service_call(call.base, NULL);
		return;
	}
	if (!matched) {
		ipc_error_ProfileNotMatched_close_service_call(call.base, NULL);
		return;
	}
}

static const struct ipc_handler ipc_handler = {
	.Reload = handle_reload,
	.Switch = handle_switch,
	.Status = handle_status,
};

static int set_cloexec(int fd) {
	int flags = fcntl(fd, F_GETFD);
	if (flags < 0) {
		perror("fnctl(F_GETFD) failed");
		return -1;
	}
	if (fcntl(fd, F_SETFD, flags | O_CLOEXEC) < 0) {
		perror("fnctl(F_SETFD) failed");
		return -1;
	}
	return 0;
}

int kanshi_init_ipc(struct kanshi_state *state, int listen_fd) {
	if (listen_fd >= 0 && set_cloexec(listen_fd) < 0) {
		return -1;
	}

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

	struct vali_registry *registry = vali_registry_create(&(struct vali_registry_options){
		.vendor = "emersion",
		.product = "kanshi",
		.version = KANSHI_VERSION,
		.url = "https://wayland.emersion.fr/kanshi/",
	});
	if (registry == NULL) {
		fprintf(stderr, "Failed to create Varlink registry\n");
		return -1;
	}

	vali_registry_add(registry, &ipc_interface, ipc_get_call_handler(&ipc_handler));
	vali_service_set_call_handler(service, vali_registry_get_call_handler(registry));
	vali_service_set_user_data(service, state);

	if (listen_fd >= 0) {
		if (!vali_service_listen_fd(service, listen_fd)) {
			fprintf(stderr, "Failed to start Varlink listener from FD\n");
			return -1;
		}
	} else {
		char path[PATH_MAX];
		if (get_ipc_path(path, sizeof(path)) < 0) {
			return -1;
		}
		unlink(path);
		if (!vali_service_listen_unix(service, path)) {
			fprintf(stderr, "Failed to start Varlink listener on %s\n", path);
			return -1;
		}
	}

	state->service = service;
	state->registry = registry;

	return 0;
}

void kanshi_finish_ipc(struct kanshi_state *state) {
	if (state->service) {
		vali_service_destroy(state->service);
		state->service = NULL;
	}
	if (state->registry) {
		vali_registry_destroy(state->registry);
		state->registry = NULL;
	}
}
