/*
 * Copyright © 2023 Sergio Gómez for Collabora Ltd
 *
 * Permission is hereby granted, free of charge, to any person obtaining a
 * copy of this software and associated documentation files (the "Software"),
 * to deal in the Software without restriction, including without limitation
 * the rights to use, copy, modify, merge, publish, distribute, sublicense,
 * and/or sell copies of the Software, and to permit persons to whom the
 * Software is furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice (including the next
 * paragraph) shall be included in all copies or substantial portions of the
 * Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL
 * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
 * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
 * DEALINGS IN THE SOFTWARE.
 */

#include "config.h"

#include <stdio.h>
#include <stdbool.h>
#include <getopt.h>
#include <sys/mman.h>
#include <linux/input.h>
#include <wayland-cursor.h>

#include "shared/helpers.h"
#include "shared/xalloc.h"
#include "shared/os-compatibility.h"
#include "shared/cairo-util.h"

#include "xdg-shell-client-protocol.h"
#include "pointer-constraints-unstable-v1-client-protocol.h"
#include "relative-pointer-unstable-v1-client-protocol.h"

#define NUM_BUFFERS 3

#define SURFACE_WIDTH 500
#define SURFACE_HEIGHT 400
#define SUBSURFACE_WIDTH (SURFACE_WIDTH / 4)
#define SUBSURFACE_HEIGHT (SURFACE_HEIGHT / 4)
#define SUBSURFACE_X_POS ((SURFACE_WIDTH-SUBSURFACE_WIDTH) / 2)
#define SUBSURFACE_Y_POS ((SURFACE_HEIGHT-SUBSURFACE_HEIGHT) / 2)

static float rgb_surface[3] = { 1, 1, 1 };
static float rgb_subsurface[3] = { 0.5, 0.5, 0.5 };

struct buffer {
	struct wl_buffer *wl_buffer;
	bool used;
	void *data;
	cairo_surface_t *cairo_surface;
};

struct shm_pool {
	struct wl_shm_pool *wl_shm_pool;
	size_t size;
	void *data;
	struct buffer buffers[NUM_BUFFERS];
};

struct surface {
	struct constraints *constraints;

	struct wl_surface *wl_surface;
	struct wl_callback *cb;
	struct wl_subsurface *wl_subsurface;

	struct wl_surface *wl_surface_cursor;
	uint32_t serial;

	int width, height, stride;
	float rgb[3];

	struct {
		int32_t x1, y1;
		int32_t x2, y2;
	} line;

	struct shm_pool pool;
	struct buffer *current_buffer;

	bool needs_reset;
	bool needs_redraw;
};

struct window {
	struct wl_display *wl_display;
	struct wl_registry *wl_registry;
	struct wl_compositor *wl_compositor;
	struct wl_subcompositor *wl_subcompositor;
	struct wl_seat *wl_seat;
	struct wl_pointer *wl_pointer;
	struct wl_keyboard *wl_keyboard;
	struct wl_shm *wl_shm;
	struct wl_cursor_theme *wl_cursor_theme;
	struct wl_cursor *wl_cursor;

	struct xdg_wm_base *xdg_wm_base;
	struct xdg_surface *xdg_surface;
	struct xdg_toplevel *xdg_toplevel;

	struct surface surface;
	struct surface subsurface;
	struct surface *surface_constrained;

};

enum constraints_state {
	STATE_UNCONSTRAINED,
	STATE_CONFINED,
	STATE_LOCKED
};

struct constraints {
	struct window window;

	struct zwp_relative_pointer_manager_v1 *zwp_relative_pointer_manager;
	struct zwp_pointer_constraints_v1 *zwp_pointer_constraints;
	struct zwp_confined_pointer_v1 *zwp_confined_pointer;
	struct zwp_relative_pointer_v1 *zwp_relative_pointer;
	struct zwp_locked_pointer_v1 *zwp_locked_pointer;

	bool running;

	bool argb8888_supported;

	enum constraints_state state;
};

/*
 * Forward declarations for functions used by Wayland event handlers (since
 * these events are defined first).
 */
static void surface_redraw(struct surface *surface);
static void surface_set_cursor(const struct surface *surface, bool hide);
static void constraints_reset_state(struct constraints *constraints);
void (*constraints_transition_state[])(struct constraints *constraints);


static void
relative_pointer_handle_motion(void *data, struct zwp_relative_pointer_v1 *pointer,
			       uint32_t utime_hi, uint32_t utime_lo,
			       wl_fixed_t dx, wl_fixed_t dy,
			       wl_fixed_t dx_unaccel, wl_fixed_t dy_unaccel)
{
	struct constraints *c = data;
	struct surface *sc = c->window.surface_constrained;

	sc->line.x2 = sc->line.x1 + wl_fixed_to_double(dx);
	sc->line.y2 = sc->line.y1 + wl_fixed_to_double(dy);

	c->window.surface_constrained->needs_reset = true;
	c->window.surface_constrained->needs_redraw = true;
}

static const struct zwp_relative_pointer_v1_listener relative_pointer_listener = {
	relative_pointer_handle_motion,
};

static void
pointer_locked(void *data, struct zwp_locked_pointer_v1 *locked_pointer)
{
	struct constraints *c = data;
	struct surface *sc = c->window.surface_constrained;

	c->state = STATE_LOCKED;
	c->zwp_locked_pointer = locked_pointer;

	sc->line.x1 = sc->line.x2;
	sc->line.y1 = sc->line.y2;
	sc->needs_reset = true;
	sc->needs_redraw = true;

	surface_set_cursor(sc, true);

	c->zwp_relative_pointer =
		zwp_relative_pointer_manager_v1_get_relative_pointer(
				c->zwp_relative_pointer_manager,
				c->window.wl_pointer);
	zwp_relative_pointer_v1_add_listener(c->zwp_relative_pointer,
					     &relative_pointer_listener, c);
}

static void
pointer_unlocked(void *data, struct zwp_locked_pointer_v1 *locked_pointer)
{
	struct constraints *c = data;

	constraints_reset_state(c);
}

static const struct zwp_locked_pointer_v1_listener locked_pointer_listener = {
	pointer_locked,
	pointer_unlocked,
};

static void
pointer_confined(void *data, struct zwp_confined_pointer_v1 *confined_pointer)
{
	struct constraints *c = data;

	c->state = STATE_CONFINED;
	c->zwp_confined_pointer = confined_pointer;
}

static void
pointer_unconfined(void *data, struct zwp_confined_pointer_v1 *confined_pointer)
{
	struct constraints *c = data;

	constraints_reset_state(c);
}

static const struct zwp_confined_pointer_v1_listener confined_pointer_listener = {
	pointer_confined,
	pointer_unconfined,
};

static void
pointer_handle_enter(void *data, struct wl_pointer *pointer, uint32_t serial,
		     struct wl_surface *surface, wl_fixed_t sx_w, wl_fixed_t sy_w)
{
	struct constraints *c = data;
	struct surface *surface_constrained =
		(surface == c->window.surface.wl_surface) ?
		&c->window.surface :
		&c->window.subsurface;

	surface_constrained->serial = serial;

	surface_set_cursor(surface_constrained, false);

	c->window.surface_constrained = surface_constrained;
}

static void
pointer_handle_leave(void *data, struct wl_pointer *pointer, uint32_t serial,
		     struct wl_surface *wl_surface)
{
	struct constraints *c = data;
	struct surface *sc = c->window.surface_constrained;

	sc->line.x1 = -1;
	sc->line.y1 = -1;
	sc->line.x2 = -1;
	sc->line.y2 = -1;
	sc->needs_reset = true;
	sc->needs_redraw = true;

	if (c->state != STATE_UNCONSTRAINED)
		constraints_reset_state(c);
}

static void
pointer_handle_motion(void *data, struct wl_pointer *pointer, uint32_t time,
		      wl_fixed_t sx_w, wl_fixed_t sy_w)
{
	struct surface *sc = ((struct constraints *)data)->window.surface_constrained;

	sc->line.x2 = wl_fixed_to_double(sx_w);
	sc->line.y2 = wl_fixed_to_double(sy_w);

	sc->needs_redraw = true;
}

static void
pointer_handle_button(void *data, struct wl_pointer *pointer, uint32_t serial,
		      uint32_t time, uint32_t btn, uint32_t button_state)
{
	struct constraints *c = data;

	if (button_state == WL_POINTER_BUTTON_STATE_RELEASED || btn != BTN_LEFT)
		return;

	constraints_transition_state[c->state](c);
}

static void
pointer_handle_axis(void *data, struct wl_pointer *pointer, uint32_t time,
		    uint32_t axis, wl_fixed_t value)
{
}

static void
pointer_handle_frame(void *data, struct wl_pointer *pointer)
{
}

static void
pointer_handle_axis_source(void *data, struct wl_pointer *pointer,
			   uint32_t source)
{
}

static void
pointer_handle_axis_stop(void *data, struct wl_pointer *pointer, uint32_t time,
			 uint32_t axis)
{
}

static void
pointer_handle_axis_discrete(void *data, struct wl_pointer *pointer,
			     uint32_t axis, int32_t discrete)
{
}

static const struct wl_pointer_listener pointer_listener = {
	pointer_handle_enter,
	pointer_handle_leave,
	pointer_handle_motion,
	pointer_handle_button,
	pointer_handle_axis,
	pointer_handle_frame,
	pointer_handle_axis_source,
	pointer_handle_axis_stop,
	pointer_handle_axis_discrete,
};

static void
keyboard_handle_keymap(void *data, struct wl_keyboard *keyboard, uint32_t format,
		       int fd, uint32_t size)
{
	close(fd);
}

static void
keyboard_handle_enter(void *data, struct wl_keyboard *keyboard, uint32_t serial,
		      struct wl_surface *surface, struct wl_array *keys)
{
}

static void
keyboard_handle_leave(void *data, struct wl_keyboard *keyboard, uint32_t serial,
		      struct wl_surface *surface)
{
}

static void
keyboard_handle_key(void *data, struct wl_keyboard *keyboard, uint32_t serial,
		    uint32_t time, uint32_t key, uint32_t state)
{
	struct constraints *c = data;

	if (key == KEY_ESC && state)
		c->running = false;
}

static void
keyboard_handle_modifiers(void *data, struct wl_keyboard *keyboard,
			  uint32_t serial, uint32_t mods_depressed,
			  uint32_t mods_latched, uint32_t mods_locked,
			  uint32_t group)
{
}

static const struct wl_keyboard_listener keyboard_listener = {
	keyboard_handle_keymap,
	keyboard_handle_enter,
	keyboard_handle_leave,
	keyboard_handle_key,
	keyboard_handle_modifiers,
};

static void
seat_handle_capabilities(void *data, struct wl_seat *wl_seat,
			 enum wl_seat_capability caps)
{
	struct constraints *c = data;

	if ((caps & WL_SEAT_CAPABILITY_POINTER) && !c->window.wl_pointer) {
		c->window.wl_pointer = wl_seat_get_pointer(wl_seat);
		wl_pointer_add_listener(c->window.wl_pointer, &pointer_listener, c);
	}
	if ((caps & WL_SEAT_CAPABILITY_KEYBOARD) && !c->window.wl_keyboard) {
		c->window.wl_keyboard = wl_seat_get_keyboard(wl_seat);
		wl_keyboard_add_listener(c->window.wl_keyboard, &keyboard_listener, c);
	}
}

static const struct wl_seat_listener seat_listener = {
	seat_handle_capabilities,
};

static void
handle_xdg_surface_configure(void *data, struct xdg_surface *surface,
			     uint32_t serial)
{
	struct constraints *c = data;

	xdg_surface_ack_configure(surface, serial);

	surface_redraw(&c->window.surface);
	surface_redraw(&c->window.subsurface);

}

static const struct xdg_surface_listener xdg_surface_listener = {
	handle_xdg_surface_configure,
};

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

static const struct xdg_wm_base_listener wm_base_listener = {
	xdg_wm_base_ping,
};

static void
shm_format(void *data, struct wl_shm *wl_shm, uint32_t format)
{
	struct constraints *c = data;

	if (format == WL_SHM_FORMAT_ARGB8888)
		c->argb8888_supported = true;
}

struct wl_shm_listener shm_listener = {
	shm_format
};

static void
handle_global(void *data, struct wl_registry *registry, uint32_t name,
	      const char *interface, uint32_t version)
{
	struct constraints *c = data;

	if (strcmp(interface, "wl_compositor") == 0) {
		c->window.wl_compositor =
			wl_registry_bind(registry, name,
					 &wl_compositor_interface, 1);
	} else if (strcmp(interface, "xdg_wm_base") == 0) {
		c->window.xdg_wm_base =
			wl_registry_bind(registry, name,
					 &xdg_wm_base_interface, 1);
		xdg_wm_base_add_listener(c->window.xdg_wm_base,
					 &wm_base_listener, c);
	} else if (strcmp(interface, "wl_shm") == 0) {
		c->window.wl_shm = wl_registry_bind(registry, name,
					      &wl_shm_interface, 1);
		wl_shm_add_listener(c->window.wl_shm, &shm_listener, c);
		c->window.wl_cursor_theme = wl_cursor_theme_load(NULL, 32, c->window.wl_shm);
		c->window.wl_cursor =
			wl_cursor_theme_get_cursor(c->window.wl_cursor_theme, "left_ptr");
	} else if (strcmp(interface, "wl_seat") == 0) {
		c->window.wl_seat = wl_registry_bind(c->window.wl_registry, name,
						       &wl_seat_interface, 1);
		wl_seat_add_listener(c->window.wl_seat, &seat_listener,
				     c);
	} else if (strcmp(interface, "zwp_relative_pointer_manager_v1") == 0) {
		c->zwp_relative_pointer_manager =
			wl_registry_bind(registry, name,
					 &zwp_relative_pointer_manager_v1_interface,
					 1);
	} else if (strcmp(interface, "zwp_pointer_constraints_v1") == 0)  {
		c->zwp_pointer_constraints =
			wl_registry_bind(registry, name,
					 &zwp_pointer_constraints_v1_interface,
					 1);
	} else if (strcmp(interface, wl_subcompositor_interface.name) == 0) {
		c->window.wl_subcompositor =
			wl_registry_bind(registry, name,
					 &wl_subcompositor_interface, 1);
	}
}

static void
handle_global_remove(void *data, struct wl_registry *registry, uint32_t name)
{
}

static const struct wl_registry_listener registry_listener = {
	handle_global,
	handle_global_remove
};

static void
handle_toplevel_configure(void *data, struct xdg_toplevel *xdg_toplevel,
			      int32_t width, int32_t height,
			      struct wl_array *state)
{
}

static void
handle_toplevel_close(void *data, struct xdg_toplevel *xdg_toplevel)
{
	struct constraints *c = data;
	c->running = false;
}

static const struct xdg_toplevel_listener xdg_toplevel_listener = {
	handle_toplevel_configure,
	handle_toplevel_close,
};

static void
buffer_release(void *data, struct wl_buffer *wl_buffer)
{
	struct buffer *buffer = data;

	buffer->used = false;
}

static const struct wl_buffer_listener buffer_listener = {
	buffer_release
};

static void
frame_done(void *data, struct wl_callback *cb, uint32_t time)
{
	struct surface *surface = data;

	wl_callback_destroy(cb);

	surface_redraw(surface);
}

static const struct wl_callback_listener frame_listener = {
	.done = frame_done,
};

static struct buffer *
shm_pool_get_buffer(struct shm_pool *pool, const struct surface *surface)
{
	size_t i, offset;
	struct buffer *buffer = surface->current_buffer;
	cairo_surface_t *prev_surface = buffer ? buffer->cairo_surface : NULL;
	cairo_t *cr;

	/* reuse current buffer if possible */
	if (buffer && !buffer->used)
		return buffer;

	buffer = NULL;
	for (i = 0; i < ARRAY_LENGTH(pool->buffers); i++) {
		if (!pool->buffers[i].used) {
			buffer = &pool->buffers[i];
			/* ARRAY_LENGTH(pool->buffers) is a factor of pool->size */
			offset = pool->size / ARRAY_LENGTH(pool->buffers) * i;
			break;
		}
	}
	if (!buffer) {
		fprintf(stderr, "no buffers available\n");
		return NULL;
	}

	if (!buffer->wl_buffer) {
		buffer->wl_buffer = wl_shm_pool_create_buffer(pool->wl_shm_pool,
							      offset,
							      surface->width,
							      surface->height,
							      surface->stride,
							      WL_SHM_FORMAT_ARGB8888);
		wl_buffer_add_listener(buffer->wl_buffer,
				       &buffer_listener, buffer);

		buffer->data = (char *)pool->data + offset;
		buffer->cairo_surface =
			cairo_image_surface_create_for_data(buffer->data,
							    CAIRO_FORMAT_ARGB32,
							    surface->width,
							    surface->height,
							    surface->stride);

		if (cairo_surface_status(buffer->cairo_surface) !=
		    CAIRO_STATUS_SUCCESS) {
			fprintf(stderr, "cairo failed to create surface\n");
			wl_buffer_destroy(buffer->wl_buffer);
			buffer->data = NULL;
			return NULL;
		}
	}

	/* we need to preserve the contents of the old buffer, if any */
	if (prev_surface) {
		cr = cairo_create(buffer->cairo_surface);
		cairo_set_operator(cr, CAIRO_OPERATOR_SOURCE);
		cairo_set_source_surface(cr, prev_surface, 0, 0);
		cairo_rectangle(cr, 0, 0, surface->width, surface->height);
		cairo_clip(cr);
		cairo_paint(cr);
		cairo_destroy(cr);
	}

	return buffer;
}

static void
shm_pool_deinit(const struct shm_pool *pool)
{
	size_t i;

	for (i = 0; i < ARRAY_LENGTH(pool->buffers); i++) {
		const struct buffer *b = &pool->buffers[i];

		if (b->wl_buffer) {
			wl_buffer_destroy(b->wl_buffer);
			cairo_surface_destroy(b->cairo_surface);
		}
	}

	if (pool->data) {
		if (munmap(pool->data, pool->size) < 0) {
			fprintf(stderr, "failed to unmap: %s\n", strerror(errno));
		}
		wl_shm_pool_destroy(pool->wl_shm_pool);
	}
}

static int
shm_pool_init(struct shm_pool *pool, size_t size, struct wl_shm *wl_shm)
{
	size_t pool_size = size * ARRAY_LENGTH(pool->buffers);
	int fd = os_create_anonymous_file(pool_size);

	if (fd < 0) {
		fprintf(stderr, "creating a buffer file for %zu B failed: %s\n",
			pool_size, strerror(errno));
		return -1;
	}

	pool->data = mmap(NULL, pool_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
	if (pool->data == MAP_FAILED) {
		fprintf(stderr, "mmap failed: %s\n", strerror(errno));
		close(fd);
		return -1;
	}

	pool->wl_shm_pool = wl_shm_create_pool(wl_shm, fd, pool_size);

	close(fd);

	pool->size = pool_size;

	return 0;
}

static void
surface_set_cursor(const struct surface *surface, bool hide)
{
	const struct constraints *c = surface->constraints;
	struct wl_cursor_image *image = c->window.wl_cursor->images[0];
	struct wl_buffer *buffer = wl_cursor_image_get_buffer(image);

	wl_pointer_set_cursor(c->window.wl_pointer, surface->serial,
			      hide ? NULL : surface->wl_surface_cursor,
			      image->hotspot_x, image->hotspot_y);

	if (!hide) {
		wl_surface_attach(surface->wl_surface_cursor, buffer, 0, 0);
		wl_surface_damage(surface->wl_surface_cursor, 0, 0,
				  image->width, image->height);
	}

	wl_surface_commit(surface->wl_surface_cursor);
}

static void
surface_draw_line(struct surface *surface, const enum constraints_state state)
{
	if (surface->line.x1 != -1 && surface->line.y1 != -1) {
		cairo_t *cr = cairo_create(surface->current_buffer->cairo_surface);

		cairo_set_line_width(cr, 2.0);
		cairo_set_source_rgb(cr, 0, 0, 0);
		cairo_move_to(cr, surface->line.x1, surface->line.y1);
		cairo_line_to(cr, surface->line.x2, surface->line.y2);
		cairo_stroke(cr);
		cairo_destroy(cr);
	}

	if (state != STATE_LOCKED) {
		surface->line.x1 = surface->line.x2;
		surface->line.y1 = surface->line.y2;
	}
}

static void
surface_reset(const struct surface *surface)
{
	cairo_t *cr = cairo_create(surface->current_buffer->cairo_surface);

	cairo_rectangle(cr, 0, 0, surface->width, surface->height);
	cairo_set_source_rgb(cr, surface->rgb[0], surface->rgb[1],
			     surface->rgb[2]);
	cairo_fill(cr);
	cairo_destroy(cr);
}

static void
surface_redraw(struct surface *surface)
{
	struct buffer *buffer;

	if (!(buffer = shm_pool_get_buffer(&surface->pool, surface)))
		return;

	surface->current_buffer = buffer;

	surface->cb = wl_surface_frame(surface->wl_surface);
	wl_callback_add_listener(surface->cb, &frame_listener, surface);

	if (surface->needs_redraw) {
		if (surface->needs_reset) {
			surface_reset(surface);
			surface->needs_reset = false;
		}
		surface_draw_line(surface, surface->constraints->state);
		surface->needs_redraw = false;

		buffer->used = true;
		wl_surface_attach(surface->wl_surface, buffer->wl_buffer, 0, 0);
		wl_surface_damage(surface->wl_surface, 0, 0, surface->width, surface->height);
	}

	wl_surface_commit(surface->wl_surface);
}

static void
surface_deinit(const struct surface *surface)
{
	shm_pool_deinit(&surface->pool);

	if (surface->wl_subsurface)
		wl_subsurface_destroy(surface->wl_subsurface);
	if (surface->wl_surface)
		wl_surface_destroy(surface->wl_surface);
	if (surface->wl_surface_cursor)
		wl_surface_destroy(surface->wl_surface_cursor);

	if (surface->cb)
		wl_callback_destroy(surface->cb);
}

static int
surface_init(struct surface *surface, struct constraints *constraints,
	     const float *rgb, int width, int height,
	     const struct surface *parent_surface, int pos_x, int pos_y)
{
	struct window *window = &constraints->window;

	surface->wl_surface = wl_compositor_create_surface(window->wl_compositor);

	surface->rgb[0] = rgb[0];
	surface->rgb[1] = rgb[1];
	surface->rgb[2] = rgb[2];

	surface->width = width;
	surface->height = height;
	surface->stride = cairo_format_stride_for_width(CAIRO_FORMAT_ARGB32,
							surface->width);
	surface->line.x1 = surface->line.y1 = -1;
	surface->line.x2 = surface->line.y2 = -1;

	surface->needs_reset = true;
	surface->needs_redraw = true;

	if (parent_surface) {
		surface->wl_subsurface =
			wl_subcompositor_get_subsurface(window->wl_subcompositor,
							surface->wl_surface,
							parent_surface->wl_surface);
		wl_subsurface_set_position(window->subsurface.wl_subsurface,
					   pos_x, pos_y);
	}

	surface->wl_surface_cursor = wl_compositor_create_surface(window->wl_compositor);

	surface->constraints = constraints;

	return shm_pool_init(&surface->pool, surface->stride * surface->height,
			     window->wl_shm);
}

static void
constraints_reset_state(struct constraints *constraints)
{
	if (constraints->zwp_confined_pointer) {
		zwp_confined_pointer_v1_destroy(constraints->zwp_confined_pointer);
		constraints->zwp_confined_pointer = NULL;
	}
	if (constraints->zwp_relative_pointer) {
		zwp_relative_pointer_v1_destroy(constraints->zwp_relative_pointer);
		constraints->zwp_relative_pointer = NULL;
	}
	if (constraints->zwp_locked_pointer) {
		zwp_locked_pointer_v1_destroy(constraints->zwp_locked_pointer);
		constraints->zwp_locked_pointer = NULL;
	}

	constraints->state = STATE_UNCONSTRAINED;
}

static void
constraints_transition_unconstrained(struct constraints *constraints)
{
	const struct surface *sc = constraints->window.surface_constrained;
	struct zwp_confined_pointer_v1 *confined_pointer;

	confined_pointer =
		zwp_pointer_constraints_v1_confine_pointer(constraints->zwp_pointer_constraints,
							   sc->wl_surface,
							   constraints->window.wl_pointer,
							   NULL,
							   ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);

	zwp_confined_pointer_v1_add_listener(confined_pointer,
					     &confined_pointer_listener,
					     constraints);
}

static void
constraints_transition_confined(struct constraints *constraints)
{
	const struct surface *sc = constraints->window.surface_constrained;
	struct zwp_locked_pointer_v1 *locked_pointer;

	zwp_confined_pointer_v1_destroy(constraints->zwp_confined_pointer);
	constraints->zwp_confined_pointer = NULL;

	locked_pointer =
		zwp_pointer_constraints_v1_lock_pointer(constraints->zwp_pointer_constraints,
							sc->wl_surface,
							constraints->window.wl_pointer,
							NULL,
							ZWP_POINTER_CONSTRAINTS_V1_LIFETIME_ONESHOT);

	zwp_locked_pointer_v1_add_listener(locked_pointer,
					   &locked_pointer_listener,
					   constraints);
}

static void
constraints_transition_locked(struct constraints *constraints)
{
	struct surface *sc = constraints->window.surface_constrained;

	constraints_reset_state(constraints);

	surface_set_cursor(sc, false);
}

void (*constraints_transition_state[])(struct constraints *constraints) =
{
	constraints_transition_unconstrained,
	constraints_transition_confined,
	constraints_transition_locked
};

static void
constraints_deinit(const struct constraints *constraints)
{
	surface_deinit(&constraints->window.subsurface);
	surface_deinit(&constraints->window.surface);

	if (constraints->zwp_locked_pointer)
		zwp_locked_pointer_v1_destroy(constraints->zwp_locked_pointer);
	if (constraints->zwp_confined_pointer)
		zwp_confined_pointer_v1_destroy(constraints->zwp_confined_pointer);
	if (constraints->zwp_relative_pointer)
		zwp_relative_pointer_v1_destroy(constraints->zwp_relative_pointer);
	if (constraints->zwp_relative_pointer_manager)
		zwp_relative_pointer_manager_v1_destroy(constraints->zwp_relative_pointer_manager);
	if (constraints->zwp_pointer_constraints)
		zwp_pointer_constraints_v1_destroy(constraints->zwp_pointer_constraints);

	if (constraints->window.xdg_toplevel)
		xdg_toplevel_destroy(constraints->window.xdg_toplevel);
	if (constraints->window.xdg_surface)
		xdg_surface_destroy(constraints->window.xdg_surface);
	if (constraints->window.xdg_wm_base)
		xdg_wm_base_destroy(constraints->window.xdg_wm_base);

	if (constraints->window.wl_cursor_theme)
		wl_cursor_theme_destroy(constraints->window.wl_cursor_theme);
	if (constraints->window.wl_shm)
		wl_shm_destroy(constraints->window.wl_shm);
	if (constraints->window.wl_keyboard)
		wl_keyboard_destroy(constraints->window.wl_keyboard);
	if (constraints->window.wl_pointer)
		wl_pointer_destroy(constraints->window.wl_pointer);
	if (constraints->window.wl_seat)
		wl_seat_destroy(constraints->window.wl_seat);
	if (constraints->window.wl_subcompositor)
		wl_subcompositor_destroy(constraints->window.wl_subcompositor);
	if (constraints->window.wl_compositor)
		wl_compositor_destroy(constraints->window.wl_compositor);
	if (constraints->window.wl_registry)
		wl_registry_destroy(constraints->window.wl_registry);
	if (constraints->window.wl_display)
		wl_display_disconnect(constraints->window.wl_display);
}

static int
constraints_check_globals(const struct constraints *c)
{
	if (!c->window.xdg_wm_base) {
		fprintf(stderr, "no xdg-shell found\n");
		return -1;
	}
	if (!c->zwp_pointer_constraints) {
		fprintf(stderr, "no pointer constraints found\n");
		return -1;
	}
	if (!c->zwp_relative_pointer_manager) {
		fprintf(stderr, "no relative pointer manager found\n");
		return -1;
	}
	if (!c->window.wl_seat || !c->window.wl_pointer || !c->window.wl_keyboard) {
		fprintf(stderr, "no valid seat found\n");
		return -1;
	}
	if (!c->argb8888_supported) {
		fprintf(stderr, "WL_SHM_FORMAT_ARGB8888 not supported\n");
		return -1;
	}

	return 0;
}

static int
constraints_init(struct constraints *constraints)
{
	struct window *window = &constraints->window;

	window->wl_display = wl_display_connect(NULL);
	if (!window->wl_display) {
		fprintf(stderr, "failed to connect to Wayland display: %s\n",
			strerror(errno));
		goto fail;
	}

	/* get globals */
	window->wl_registry = wl_display_get_registry(window->wl_display);
	wl_registry_add_listener(window->wl_registry, &registry_listener,
				 constraints);
	/* double roundtrip to get all initial events */
	if (wl_display_roundtrip(window->wl_display) == -1 ||
	    wl_display_roundtrip(window->wl_display) == -1) {
		fprintf(stderr, "wl_display_roundtrip() failed: %s\n",
			strerror(errno));
		goto fail;
	}
	if (constraints_check_globals(constraints) == -1) {
		goto fail;
	}

	/* surface setup */
	if (surface_init(&window->surface, constraints, rgb_surface, SURFACE_WIDTH,
			 SURFACE_HEIGHT, NULL, 0, 0) == -1) {
		fprintf(stderr, "failed to initialize surface\n");
		goto fail;
	}

	/* xdg surface setup */
	window->xdg_surface = xdg_wm_base_get_xdg_surface(window->xdg_wm_base,
							  window->surface.wl_surface);
	xdg_surface_add_listener(window->xdg_surface, &xdg_surface_listener,
				 constraints);
	window->xdg_toplevel = xdg_surface_get_toplevel(window->xdg_surface);
	xdg_toplevel_add_listener(window->xdg_toplevel, &xdg_toplevel_listener,
				  constraints);
	xdg_toplevel_set_title(window->xdg_toplevel, "simple-constraints");
	xdg_toplevel_set_app_id(window->xdg_toplevel, "org.freedesktop.weston.simple-contrains");

	/* sub-surface setup */
	if (surface_init(&window->subsurface, constraints, rgb_subsurface,
			 SUBSURFACE_WIDTH, SUBSURFACE_HEIGHT, &window->surface,
			 SUBSURFACE_X_POS, SUBSURFACE_Y_POS) == -1) {
		fprintf(stderr, "failed to initialize sub-surface\n");
		goto fail;
	}

	wl_surface_commit(window->surface.wl_surface);

	constraints->running = true;

	return 0;
fail:
	if (constraints)
		constraints_deinit(constraints);
	return -1;
}

static void
print_usage(void)
{
	printf("\nDemo client that showcases the Wayland Pointer Constraints"
	       " protocol:\n\n");
	printf("The gray rectangle represents a Wayland subsurface for the"
	       " surface in white.\n");
	printf("Click on any surface to cycle between unconstrained-confined"
	       "-locked states.\n\n");
}

int
main()
{
	static struct constraints constraints;
	int ret = 0;

	print_usage();

	if (constraints_init(&constraints) < 0)
		return -1;

	while (ret != -1 && constraints.running)
		ret = wl_display_dispatch(constraints.window.wl_display);

	constraints_deinit(&constraints);

	return ret;
}
