/*
 * Copyright © 2014 David FORT <contact@hardening-consulting.com>
 *
 * Permission to use, copy, modify, distribute, and sell this software and its
 * documentation for any purpose is hereby granted without fee, provided that
 * the above copyright notice appear in all copies and that both that copyright
 * notice and this permission notice appear in supporting documentation, and
 * that the name of the copyright holders not be used in advertising or
 * publicity pertaining to distribution of the software without specific,
 * written prior permission.  The copyright holders make no representations
 * about the suitability of this software for any purpose.  It is provided "as
 * is" without express or implied warranty.
 *
 * THE COPYRIGHT HOLDERS DISCLAIM ALL WARRANTIES WITH REGARD TO THIS SOFTWARE,
 * INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS, IN NO
 * EVENT SHALL THE COPYRIGHT HOLDERS BE LIABLE FOR ANY SPECIAL, INDIRECT OR
 * CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE,
 * DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER
 * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE
 * OF THIS SOFTWARE.
 */
#include "uwac-priv.h"
#include "uwac-utils.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdarg.h>
#include <assert.h>
#include <errno.h>
#include <time.h>
#include <unistd.h>
#include <sys/epoll.h>

#include "uwac-os.h"
#include "wayland-cursor.h"

#define TARGET_COMPOSITOR_INTERFACE 3U
#define TARGET_SHM_INTERFACE 1U
#define TARGET_SHELL_INTERFACE 1U
#define TARGET_DDM_INTERFACE 1U
#define TARGET_SEAT_INTERFACE 5U
#define TARGET_XDG_VERSION 5U /* The version of xdg-shell that we implement */

#if !defined(NDEBUG)
static const char* event_names[] = {
	"new seat",      "removed seat",      "new output",      "configure",    "pointer enter",
	"pointer leave", "pointer motion",    "pointer buttons", "pointer axis", "keyboard enter",
	"key",           "touch frame begin", "touch up",        "touch down",   "touch motion",
	"touch cancel",  "touch frame end",   "frame done",      "close",        nullptr
};
#endif

static bool uwac_default_error_handler(UwacDisplay* display, UwacReturnCode code, const char* msg,
                                       ...)
{
	va_list args = WINPR_C_ARRAY_INIT;
	va_start(args, msg);
	(void)vfprintf(stderr, "%s", args);
	va_end(args);
	return false;
}

UwacErrorHandler uwacErrorHandler = uwac_default_error_handler;

void UwacInstallErrorHandler(UwacErrorHandler handler)
{
	if (handler)
		uwacErrorHandler = handler;
	else
		uwacErrorHandler = uwac_default_error_handler;
}

static void cb_shm_format(void* data, struct wl_shm* wl_shm, uint32_t format)
{
	UwacDisplay* d = data;

	if (format == WL_SHM_FORMAT_RGB565)
		d->has_rgb565 = true;

	d->shm_formats_nb++;
	d->shm_formats =
	    xrealloc((void*)d->shm_formats, sizeof(enum wl_shm_format) * d->shm_formats_nb);
	d->shm_formats[d->shm_formats_nb - 1] = format;
}

static struct wl_shm_listener shm_listener = { cb_shm_format };

static void xdg_shell_ping(void* data, struct xdg_wm_base* xdg_wm_base, uint32_t serial)
{
	xdg_wm_base_pong(xdg_wm_base, serial);
}

static const struct xdg_wm_base_listener xdg_wm_base_listener = {
	xdg_shell_ping,
};

#ifdef BUILD_FULLSCREEN_SHELL
static void fullscreen_capability(void* data,
                                  struct zwp_fullscreen_shell_v1* zwp_fullscreen_shell_v1,
                                  uint32_t capability)
{
}

static const struct zwp_fullscreen_shell_v1_listener fullscreen_shell_listener = {
	fullscreen_capability,
};
#endif

static void display_destroy_seat(UwacDisplay* d, uint32_t name)
{
	UwacSeat* seat = nullptr;
	UwacSeat* tmp = nullptr;
	wl_list_for_each_safe(seat, tmp, &d->seats, link)
	{
		if (seat->seat_id == name)
		{
			UwacSeatDestroy(seat);
		}
	}
}

static void UwacSeatRegisterDDM(UwacSeat* seat)
{
	UwacDisplay* d = seat->display;
	if (!d->data_device_manager)
		return;

	if (!seat->data_device)
		seat->data_device =
		    wl_data_device_manager_get_data_device(d->data_device_manager, seat->seat);
}

static void UwacRegisterCursor(UwacSeat* seat)
{
	if (!seat || !seat->display || !seat->display->compositor)
		return;

	seat->pointer_surface = wl_compositor_create_surface(seat->display->compositor);
}

static void registry_handle_global(void* data, struct wl_registry* registry, uint32_t id,
                                   const char* interface, uint32_t version)
{
	UwacDisplay* d = data;
	UwacGlobal* global = nullptr;
	global = xzalloc(sizeof *global);
	global->name = id;
	global->interface = xstrdup(interface);
	global->version = version;
	wl_list_insert(d->globals.prev, &global->link);

	if (strcmp(interface, "wl_compositor") == 0)
	{
		d->compositor = wl_registry_bind(registry, id, &wl_compositor_interface,
		                                 min(TARGET_COMPOSITOR_INTERFACE, version));
	}
	else if (strcmp(interface, "wl_shm") == 0)
	{
		d->shm =
		    wl_registry_bind(registry, id, &wl_shm_interface, min(TARGET_SHM_INTERFACE, version));
		wl_shm_add_listener(d->shm, &shm_listener, d);
	}
	else if (strcmp(interface, "wl_output") == 0)
	{
		UwacOutput* output = nullptr;
		UwacOutputNewEvent* ev = nullptr;
		output = UwacCreateOutput(d, id, version);

		if (!output)
		{
			assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create output\n"));
			return;
		}

		ev = (UwacOutputNewEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_NEW_OUTPUT);

		if (ev)
			ev->output = output;
	}
	else if (strcmp(interface, "wl_seat") == 0)
	{
		UwacSeatNewEvent* ev = nullptr;
		UwacSeat* seat = nullptr;
		seat = UwacSeatNew(d, id, min(version, TARGET_SEAT_INTERFACE));

		if (!seat)
		{
			assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create new seat\n"));
			return;
		}

		UwacSeatRegisterDDM(seat);
		UwacSeatRegisterClipboard(seat);
		UwacRegisterCursor(seat);
		ev = (UwacSeatNewEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_NEW_SEAT);

		if (!ev)
		{
			assert(uwacErrorHandler(d, UWAC_ERROR_NOMEMORY, "unable to create new seat event\n"));
			return;
		}

		ev->seat = seat;
	}
	else if (strcmp(interface, "wl_data_device_manager") == 0)
	{
		UwacSeat* seat = nullptr;
		UwacSeat* tmp = nullptr;

		d->data_device_manager = wl_registry_bind(registry, id, &wl_data_device_manager_interface,
		                                          min(TARGET_DDM_INTERFACE, version));

		wl_list_for_each_safe(seat, tmp, &d->seats, link)
		{
			UwacSeatRegisterDDM(seat);
			UwacSeatRegisterClipboard(seat);
			UwacRegisterCursor(seat);
		}
	}
	else if (strcmp(interface, "wl_shell") == 0)
	{
		d->shell = wl_registry_bind(registry, id, &wl_shell_interface,
		                            min(TARGET_SHELL_INTERFACE, version));
	}
	else if (strcmp(interface, "xdg_wm_base") == 0)
	{
		d->xdg_base = wl_registry_bind(registry, id, &xdg_wm_base_interface, 1);
		xdg_wm_base_add_listener(d->xdg_base, &xdg_wm_base_listener, d);
	}
	else if (strcmp(interface, "wp_viewporter") == 0)
	{
		d->viewporter = wl_registry_bind(registry, id, &wp_viewporter_interface, 1);
	}
	else if (strcmp(interface, "zwp_keyboard_shortcuts_inhibit_manager_v1") == 0)
	{
		d->keyboard_inhibit_manager =
		    wl_registry_bind(registry, id, &zwp_keyboard_shortcuts_inhibit_manager_v1_interface, 1);
	}
	else if (strcmp(interface, "zxdg_decoration_manager_v1") == 0)
	{
		d->deco_manager = wl_registry_bind(registry, id, &zxdg_decoration_manager_v1_interface, 1);
	}
	else if (strcmp(interface, "org_kde_kwin_server_decoration_manager") == 0)
	{
		d->kde_deco_manager =
		    wl_registry_bind(registry, id, &org_kde_kwin_server_decoration_manager_interface, 1);
	}
#if BUILD_IVI
	else if (strcmp(interface, "ivi_application") == 0)
	{
		d->ivi_application = wl_registry_bind(registry, id, &ivi_application_interface, 1);
	}
#endif
#if BUILD_FULLSCREEN_SHELL
	else if (strcmp(interface, "zwp_fullscreen_shell_v1") == 0)
	{
		d->fullscreen_shell = wl_registry_bind(registry, id, &zwp_fullscreen_shell_v1_interface, 1);
		zwp_fullscreen_shell_v1_add_listener(d->fullscreen_shell, &fullscreen_shell_listener, d);
	}
#endif
#if 0
	else if (strcmp(interface, "text_cursor_position") == 0)
	{
		d->text_cursor_position = wl_registry_bind(registry, id, &text_cursor_position_interface, 1);
	}
	else if (strcmp(interface, "workspace_manager") == 0)
	{
		//init_workspace_manager(d, id);
	}
	else if (strcmp(interface, "wl_subcompositor") == 0)
	{
		d->subcompositor = wl_registry_bind(registry, id, &wl_subcompositor_interface, 1);
#endif
}

static void registry_handle_global_remove(void* data, struct wl_registry* registry, uint32_t name)
{
	UwacDisplay* d = data;
	UwacGlobal* global = nullptr;
	UwacGlobal* tmp = nullptr;
	wl_list_for_each_safe(global, tmp, &d->globals, link)
	{
		if (global->name != name)
			continue;

#if 0

		if (strcmp(global->interface, "wl_output") == 0)
			display_destroy_output(d, name);

#endif

		if (strcmp(global->interface, "wl_seat") == 0)
		{
			UwacSeatRemovedEvent* ev = nullptr;
			display_destroy_seat(d, name);
			ev = (UwacSeatRemovedEvent*)UwacDisplayNewEvent(d, UWAC_EVENT_REMOVED_SEAT);

			if (ev)
				ev->id = name;
		}

		wl_list_remove(&global->link);
		free(global->interface);
		free(global);
	}
}

static void UwacDestroyGlobal(UwacGlobal* global)
{
	free(global->interface);
	wl_list_remove(&global->link);
	free(global);
}

static void* display_bind(UwacDisplay* display, uint32_t name, const struct wl_interface* interface,
                          uint32_t version)
{
	return wl_registry_bind(display->registry, name, interface, version);
}

static const struct wl_registry_listener registry_listener = { registry_handle_global,
	                                                           registry_handle_global_remove };

int UwacDisplayWatchFd(UwacDisplay* display, int fd, uint32_t events, UwacTask* task)
{
	struct epoll_event ep;
	ep.events = events;
	ep.data.ptr = task;
	return epoll_ctl(display->epoll_fd, EPOLL_CTL_ADD, fd, &ep);
}

static void UwacDisplayUnwatchFd(UwacDisplay* display, int fd)
{
	epoll_ctl(display->epoll_fd, EPOLL_CTL_DEL, fd, nullptr);
}

static void display_exit(UwacDisplay* display)
{
	display->running = false;
}

static void display_dispatch_events(UwacTask* task, uint32_t events)
{
	UwacDisplay* display = container_of(task, UwacDisplay, dispatch_fd_task);
	struct epoll_event ep;
	int ret = 0;
	display->display_fd_events = events;

	if ((events & EPOLLERR) || (events & EPOLLHUP))
	{
		display_exit(display);
		return;
	}

	if (events & EPOLLIN)
	{
		ret = wl_display_dispatch(display->display);

		if (ret == -1)
		{
			display_exit(display);
			return;
		}
	}

	if (events & EPOLLOUT)
	{
		ret = wl_display_flush(display->display);

		if (ret == 0)
		{
			ep.events = EPOLLIN | EPOLLERR | EPOLLHUP;
			ep.data.ptr = &display->dispatch_fd_task;
			epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep);
		}
		else if (ret == -1 && errno != EAGAIN)
		{
			display_exit(display);
			return;
		}
	}
}

UwacDisplay* UwacOpenDisplay(const char* name, UwacReturnCode* err)
{
	UwacDisplay* ret = nullptr;
	ret = (UwacDisplay*)xzalloc(sizeof(*ret));

	if (!ret)
	{
		*err = UWAC_ERROR_NOMEMORY;
		return nullptr;
	}

	wl_list_init(&ret->globals);
	wl_list_init(&ret->seats);
	wl_list_init(&ret->outputs);
	wl_list_init(&ret->windows);
	ret->display = wl_display_connect(name);

	if (ret->display == nullptr)
	{
		char buffer[256] = WINPR_C_ARRAY_INIT;
		(void)fprintf(stderr, "failed to connect to Wayland display %s: %s\n", name,
		              uwac_strerror(errno, buffer, sizeof(buffer)));
		*err = UWAC_ERROR_UNABLE_TO_CONNECT;
		goto out_free;
	}

	ret->epoll_fd = uwac_os_epoll_create_cloexec();

	if (ret->epoll_fd < 0)
	{
		*err = UWAC_NOT_ENOUGH_RESOURCES;
		goto out_disconnect;
	}

	ret->display_fd = wl_display_get_fd(ret->display);
	ret->registry = wl_display_get_registry(ret->display);

	if (!ret->registry)
	{
		*err = UWAC_ERROR_NOMEMORY;
		goto out_close_epoll;
	}

	wl_registry_add_listener(ret->registry, &registry_listener, ret);

	if ((wl_display_roundtrip(ret->display) < 0) || (wl_display_roundtrip(ret->display) < 0))
	{
		uwacErrorHandler(ret, UWAC_ERROR_UNABLE_TO_CONNECT,
		                 "Failed to process Wayland connection: %m\n");
		*err = UWAC_ERROR_UNABLE_TO_CONNECT;
		goto out_free_registry;
	}

	ret->dispatch_fd_task.run = display_dispatch_events;

	if (UwacDisplayWatchFd(ret, ret->display_fd, EPOLLIN | EPOLLERR | EPOLLHUP,
	                       &ret->dispatch_fd_task) < 0)
	{
		uwacErrorHandler(ret, UWAC_ERROR_INTERNAL, "unable to watch display fd: %m\n");
		*err = UWAC_ERROR_INTERNAL;
		goto out_free_registry;
	}

	ret->running = true;
	ret->last_error = *err = UWAC_SUCCESS;
	return ret;
out_free_registry:
	wl_registry_destroy(ret->registry);
out_close_epoll:
	close(ret->epoll_fd);
out_disconnect:
	wl_display_disconnect(ret->display);
out_free:
	free(ret);
	return nullptr;
}

int UwacDisplayDispatch(UwacDisplay* display, int timeout)
{
	int ret = 0;
	int count = 0;
	UwacTask* task = nullptr;
	struct epoll_event ep[16];
	wl_display_dispatch_pending(display->display);

	if (!display->running)
		return 0;

	ret = wl_display_flush(display->display);

	if (ret < 0 && errno == EAGAIN)
	{
		ep[0].events = (EPOLLIN | EPOLLOUT | EPOLLERR | EPOLLHUP);
		ep[0].data.ptr = &display->dispatch_fd_task;
		epoll_ctl(display->epoll_fd, EPOLL_CTL_MOD, display->display_fd, &ep[0]);
	}
	else if (ret < 0)
	{
		return -1;
	}

	count = epoll_wait(display->epoll_fd, ep, ARRAY_LENGTH(ep), timeout);

	for (int i = 0; i < count; i++)
	{
		task = ep[i].data.ptr;
		task->run(task, ep[i].events);
	}

	return 1;
}

UwacReturnCode UwacDisplayGetLastError(const UwacDisplay* display)
{
	return display->last_error;
}

UwacReturnCode UwacCloseDisplay(UwacDisplay** pdisplay)
{
	UwacDisplay* display = nullptr;
	UwacSeat* seat = nullptr;
	UwacSeat* tmpSeat = nullptr;
	UwacWindow* window = nullptr;
	UwacWindow* tmpWindow = nullptr;
	UwacOutput* output = nullptr;
	UwacOutput* tmpOutput = nullptr;
	UwacGlobal* global = nullptr;
	UwacGlobal* tmpGlobal = nullptr;
	assert(pdisplay);
	display = *pdisplay;

	if (!display)
		return UWAC_ERROR_INVALID_DISPLAY;

	/* destroy windows */
	wl_list_for_each_safe(window, tmpWindow, &display->windows, link)
	{
		UwacDestroyWindow(&window);
	}
	/* destroy seats */
	wl_list_for_each_safe(seat, tmpSeat, &display->seats, link)
	{
		UwacSeatDestroy(seat);
	}
	/* destroy output */
	wl_list_for_each_safe(output, tmpOutput, &display->outputs, link)
	{
		UwacDestroyOutput(output);
	}
	/* destroy globals */
	wl_list_for_each_safe(global, tmpGlobal, &display->globals, link)
	{
		UwacDestroyGlobal(global);
	}

	if (display->compositor)
		wl_compositor_destroy(display->compositor);

	if (display->keyboard_inhibit_manager)
		zwp_keyboard_shortcuts_inhibit_manager_v1_destroy(display->keyboard_inhibit_manager);

	if (display->deco_manager)
		zxdg_decoration_manager_v1_destroy(display->deco_manager);

	if (display->kde_deco_manager)
		org_kde_kwin_server_decoration_manager_destroy(display->kde_deco_manager);

#ifdef BUILD_FULLSCREEN_SHELL

	if (display->fullscreen_shell)
		zwp_fullscreen_shell_v1_destroy(display->fullscreen_shell);

#endif
#ifdef BUILD_IVI

	if (display->ivi_application)
		ivi_application_destroy(display->ivi_application);

#endif

	if (display->xdg_toplevel)
		xdg_toplevel_destroy(display->xdg_toplevel);

	if (display->xdg_base)
		xdg_wm_base_destroy(display->xdg_base);

	if (display->shell)
		wl_shell_destroy(display->shell);

	if (display->shm)
		wl_shm_destroy(display->shm);

	if (display->viewporter)
		wp_viewporter_destroy(display->viewporter);

	if (display->subcompositor)
		wl_subcompositor_destroy(display->subcompositor);

	if (display->data_device_manager)
		wl_data_device_manager_destroy(display->data_device_manager);

	free(display->shm_formats);
	wl_registry_destroy(display->registry);
	close(display->epoll_fd);
	wl_display_disconnect(display->display);

	/* cleanup the event queue */
	while (display->push_queue)
	{
		UwacEventListItem* item = display->push_queue;
		display->push_queue = item->tail;
		free(item);
	}

	free(display);
	*pdisplay = nullptr;
	return UWAC_SUCCESS;
}

int UwacDisplayGetFd(UwacDisplay* display)
{
	return display->epoll_fd;
}

static const char* errorStrings[] = {
	"success",
	"out of memory error",
	"unable to connect to wayland display",
	"invalid UWAC display",
	"not enough resources",
	"timed out",
	"not found",
	"closed connection",

	"internal error",
};

const char* UwacErrorString(UwacReturnCode error)
{
	if (error < UWAC_SUCCESS || error >= UWAC_ERROR_LAST)
		return "invalid error code";

	return errorStrings[error];
}

UwacReturnCode UwacDisplayQueryInterfaceVersion(const UwacDisplay* display, const char* name,
                                                uint32_t* version)
{
	const UwacGlobal* global = nullptr;
	const UwacGlobal* tmp = nullptr;

	if (!display)
		return UWAC_ERROR_INVALID_DISPLAY;

	wl_list_for_each_safe(global, tmp, &display->globals, link)
	{
		if (strcmp(global->interface, name) == 0)
		{
			if (version)
				*version = global->version;

			return UWAC_SUCCESS;
		}
	}
	return UWAC_NOT_FOUND;
}

uint32_t UwacDisplayQueryGetNbShmFormats(UwacDisplay* display)
{
	if (!display)
	{
		return 0;
	}

	if (!display->shm)
	{
		display->last_error = UWAC_NOT_FOUND;
		return 0;
	}

	display->last_error = UWAC_SUCCESS;
	return display->shm_formats_nb;
}

UwacReturnCode UwacDisplayQueryShmFormats(const UwacDisplay* display, enum wl_shm_format* formats,
                                          int formats_size, int* filled)
{
	if (!display)
		return UWAC_ERROR_INVALID_DISPLAY;

	*filled = min((int64_t)display->shm_formats_nb, formats_size);
	memcpy(formats, (const void*)display->shm_formats, *filled * sizeof(enum wl_shm_format));
	return UWAC_SUCCESS;
}

uint32_t UwacDisplayGetNbOutputs(const UwacDisplay* display)
{
	return wl_list_length(&display->outputs);
}

const UwacOutput* UwacDisplayGetOutput(UwacDisplay* display, int index)
{
	int i = 0;
	int display_count = 0;
	UwacOutput* ret = nullptr;

	if (!display)
		return nullptr;

	display_count = wl_list_length(&display->outputs);
	if (display_count <= index)
		return nullptr;

	wl_list_for_each(ret, &display->outputs, link)
	{
		if (i == index)
			break;
		i++;
	}

	if (!ret)
	{
		display->last_error = UWAC_NOT_FOUND;
		return nullptr;
	}

	display->last_error = UWAC_SUCCESS;
	return ret;
}

UwacReturnCode UwacOutputGetResolution(const UwacOutput* output, UwacSize* resolution)
{
	if ((output->resolution.height <= 0) || (output->resolution.width <= 0))
		return UWAC_ERROR_INTERNAL;

	*resolution = output->resolution;
	return UWAC_SUCCESS;
}

UwacReturnCode UwacOutputGetPosition(const UwacOutput* output, UwacPosition* pos)
{
	*pos = output->position;
	return UWAC_SUCCESS;
}

UwacEvent* UwacDisplayNewEvent(UwacDisplay* display, int type)
{
	UwacEventListItem* ret = nullptr;

	if (!display)
	{
		return nullptr;
	}

	ret = xzalloc(sizeof(UwacEventListItem));

	if (!ret)
	{
		assert(uwacErrorHandler(display, UWAC_ERROR_NOMEMORY, "unable to allocate a '%s' event",
		                        event_names[type]));
		display->last_error = UWAC_ERROR_NOMEMORY;
		return nullptr;
	}

	ret->event.type = type;
	ret->tail = display->push_queue;

	if (ret->tail)
		ret->tail->head = ret;
	else
		display->pop_queue = ret;

	display->push_queue = ret;
	return &ret->event;
}

bool UwacHasEvent(UwacDisplay* display)
{
	return display->pop_queue != nullptr;
}

UwacReturnCode UwacNextEvent(UwacDisplay* display, UwacEvent* event)
{
	UwacEventListItem* prevItem = nullptr;
	int ret = 0;

	if (!display)
		return UWAC_ERROR_INVALID_DISPLAY;

	while (!display->pop_queue)
	{
		ret = UwacDisplayDispatch(display, 1 * 1000);

		if (ret < 0)
			return UWAC_ERROR_INTERNAL;
		else if (ret == 0)
			return UWAC_ERROR_CLOSED;
	}

	prevItem = display->pop_queue->head;
	*event = display->pop_queue->event;
	free(display->pop_queue);
	display->pop_queue = prevItem;

	if (prevItem)
		prevItem->tail = nullptr;
	else
		display->push_queue = nullptr;

	return UWAC_SUCCESS;
}
