/*
 * Copyright © 2018 Red Hat, Inc.
 *
 * 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 "evdev.h"

enum totem_slot_state {
	SLOT_STATE_NONE,
	SLOT_STATE_BEGIN,
	SLOT_STATE_UPDATE,
	SLOT_STATE_END,
};

struct totem_slot {
	bool dirty;
	unsigned int index;
	enum totem_slot_state state;
	struct libinput_tablet_tool *tool;
	struct tablet_axes axes;
	unsigned char changed_axes[NCHARS(LIBINPUT_TABLET_TOOL_AXIS_MAX + 1)];

	struct device_coords last_point;
};

struct totem_dispatch {
	struct evdev_dispatch base;
	struct evdev_device *device;

	int slot; /* current slot */
	struct totem_slot *slots;
	size_t nslots;

	struct evdev_device *touch_device;

	/* We only have one button */
	bool button_state_now;
	bool button_state_previous;

	enum evdev_arbitration_state arbitration_state;
};

static inline struct totem_dispatch*
totem_dispatch(struct evdev_dispatch *totem)
{
	evdev_verify_dispatch_type(totem, DISPATCH_TOTEM);

	return container_of(totem, struct totem_dispatch, base);
}

static inline struct libinput *
totem_libinput_context(const struct totem_dispatch *totem)
{
	return evdev_libinput_context(totem->device);
}

static struct libinput_tablet_tool *
totem_new_tool(struct totem_dispatch *totem)
{
	struct libinput *libinput = totem_libinput_context(totem);
	struct libinput_tablet_tool *tool;

	tool = zalloc(sizeof *tool);

	*tool = (struct libinput_tablet_tool) {
		.type = LIBINPUT_TABLET_TOOL_TYPE_TOTEM,
		.serial = 0,
		.tool_id = 0,
		.refcount = 1,
	};

	tool->pressure_offset = 0;
	tool->has_pressure_offset = false;
	tool->pressure_threshold.lower = 0;
	tool->pressure_threshold.upper = 1;

	set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_X);
	set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_Y);
	set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z);
	set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR);
	set_bit(tool->axis_caps, LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR);
	set_bit(tool->buttons, BTN_0);

	list_insert(&libinput->tool_list, &tool->link);

	return tool;
}

static inline void
totem_set_touch_device_enabled(struct totem_dispatch *totem,
			       bool enable_touch_device,
			       uint64_t time)
{
	struct evdev_device *touch_device = totem->touch_device;
	struct evdev_dispatch *dispatch;
	struct phys_rect r, *rect = NULL;
	enum evdev_arbitration_state state = ARBITRATION_NOT_ACTIVE;

	if (touch_device == NULL)
		return;

	/* We just pick the coordinates of the first touch we find. The
	 * totem only does one tool right now despite being nominally an MT
	 * device, so let's not go too hard on ourselves*/
	for (size_t i = 0; !enable_touch_device && i < totem->nslots; i++) {
		struct totem_slot *slot = &totem->slots[i];
		struct phys_coords mm;

		if (slot->state == SLOT_STATE_NONE)
			continue;

		/* Totem size is ~70mm. We could calculate the real size but
		   until we need that, hardcoding it is enough */
		mm = evdev_device_units_to_mm(totem->device, &slot->axes.point);
		r.x = mm.x - 30;
		r.y = mm.y - 30;
		r.w = 100;
		r.h = 100;

		rect = &r;

		state = ARBITRATION_IGNORE_RECT;
		break;
	}

	dispatch = touch_device->dispatch;

	if (enable_touch_device) {
	    if (dispatch->interface->touch_arbitration_toggle)
		dispatch->interface->touch_arbitration_toggle(dispatch,
							      touch_device,
							      state,
							      rect,
							      time);
	} else {
		switch (totem->arbitration_state) {
		case ARBITRATION_IGNORE_ALL:
			abort();
		case ARBITRATION_NOT_ACTIVE:
			if (dispatch->interface->touch_arbitration_toggle)
				dispatch->interface->touch_arbitration_toggle(dispatch,
									      touch_device,
									      state,
									      rect,
									      time);
			break;
		case ARBITRATION_IGNORE_RECT:
			if (dispatch->interface->touch_arbitration_update_rect)
				dispatch->interface->touch_arbitration_update_rect(dispatch,
										   touch_device,
										   rect,
										   time);
			break;
		}
	}
	totem->arbitration_state = state;
}

static void
totem_process_key(struct totem_dispatch *totem,
		  struct evdev_device *device,
		  struct input_event *e,
		  uint64_t time)
{
	switch(e->code) {
	case BTN_0:
		totem->button_state_now = !!e->value;
		break;
	default:
		evdev_log_info(device,
			       "Unhandled KEY event code %#x\n",
			       e->code);
		break;
	}
}

static void
totem_process_abs(struct totem_dispatch *totem,
		  struct evdev_device *device,
		  struct input_event *e,
		  uint64_t time)
{
	struct totem_slot *slot = &totem->slots[totem->slot];

	switch(e->code) {
	case ABS_MT_SLOT:
		if ((size_t)e->value >= totem->nslots) {
			evdev_log_bug_libinput(device,
					       "exceeded slot count (%d of max %zd)\n",
					       e->value,
					       totem->nslots);
			e->value = totem->nslots - 1;
		}
		totem->slot = e->value;
		return;
	case ABS_MT_TRACKING_ID:
		/* If the totem is already down on init, we currently
		   ignore it */
		if (e->value >= 0)
			slot->state = SLOT_STATE_BEGIN;
		else if (slot->state != SLOT_STATE_NONE)
			slot->state = SLOT_STATE_END;
		break;
	case ABS_MT_POSITION_X:
		set_bit(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X);
		break;
	case ABS_MT_POSITION_Y:
		set_bit(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y);
		break;
	case ABS_MT_TOUCH_MAJOR:
		set_bit(slot->changed_axes,
			LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR);
		break;
	case ABS_MT_TOUCH_MINOR:
		set_bit(slot->changed_axes,
			LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR);
		break;
	case ABS_MT_ORIENTATION:
		set_bit(slot->changed_axes,
			LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z);
		break;
	case ABS_MT_TOOL_TYPE:
		if (e->value != MT_TOOL_DIAL) {
			evdev_log_info(device,
				       "Unexpected tool type %#x, changing to dial\n",
				       e->code);
		}
		break;
	default:
		evdev_log_info(device,
			       "Unhandled ABS event code %#x\n",
			       e->code);
		break;
	}
}

static bool
totem_slot_fetch_axes(struct totem_dispatch *totem,
		      struct totem_slot *slot,
		      struct libinput_tablet_tool *tool,
		      struct tablet_axes *axes_out,
		      uint64_t time)
{
	struct evdev_device *device = totem->device;
	const char tmp[sizeof(slot->changed_axes)] = {0};
	struct tablet_axes axes = {0};
	struct device_float_coords delta;
	bool rc = false;

	if (memcmp(tmp, slot->changed_axes, sizeof(tmp)) == 0) {
		axes = slot->axes;
		goto out;
	}

	if (bit_is_set(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_X) ||
	    bit_is_set(slot->changed_axes, LIBINPUT_TABLET_TOOL_AXIS_Y)) {
		slot->axes.point.x = libevdev_get_slot_value(device->evdev,
							     slot->index,
							     ABS_MT_POSITION_X);
		slot->axes.point.y = libevdev_get_slot_value(device->evdev,
							     slot->index,
							     ABS_MT_POSITION_Y);
	}

	if (bit_is_set(slot->changed_axes,
		       LIBINPUT_TABLET_TOOL_AXIS_ROTATION_Z)) {
		int angle = libevdev_get_slot_value(device->evdev,
						    slot->index,
						    ABS_MT_ORIENTATION);
		/* The kernel gives us ±90 degrees off neutral */
		slot->axes.rotation = (360 - angle) % 360;
	}

	if (bit_is_set(slot->changed_axes,
		       LIBINPUT_TABLET_TOOL_AXIS_SIZE_MAJOR) ||
	    bit_is_set(slot->changed_axes,
		       LIBINPUT_TABLET_TOOL_AXIS_SIZE_MINOR)) {
		int major, minor;
		unsigned int rmajor, rminor;

		major = libevdev_get_slot_value(device->evdev,
						slot->index,
						ABS_MT_TOUCH_MAJOR);
		minor = libevdev_get_slot_value(device->evdev,
						slot->index,
						ABS_MT_TOUCH_MINOR);
		rmajor = libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MAJOR);
		rminor = libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MINOR);
		slot->axes.size.major = (double)major/rmajor;
		slot->axes.size.minor = (double)minor/rminor;
	}

	axes.point = slot->axes.point;
	axes.rotation = slot->axes.rotation;
	axes.size = slot->axes.size;

	delta.x = slot->axes.point.x - slot->last_point.x;
	delta.y = slot->axes.point.y - slot->last_point.y;
	axes.delta = filter_dispatch(device->pointer.filter, &delta, tool, time);

	rc = true;
out:
	*axes_out = axes;
	return rc;

}

static void
totem_slot_mark_all_axes_changed(struct totem_dispatch *totem,
			    struct totem_slot *slot,
			    struct libinput_tablet_tool *tool)
{
	static_assert(sizeof(slot->changed_axes) ==
			      sizeof(tool->axis_caps),
		      "Mismatching array sizes");

	memcpy(slot->changed_axes,
	       tool->axis_caps,
	       sizeof(slot->changed_axes));
}

static inline void
totem_slot_reset_changed_axes(struct totem_dispatch *totem,
			      struct totem_slot *slot)
{
	memset(slot->changed_axes, 0, sizeof(slot->changed_axes));
}

static inline void
slot_axes_initialize(struct totem_dispatch *totem,
		     struct totem_slot *slot)
{
	struct evdev_device *device = totem->device;

	slot->axes.point.x = libevdev_get_slot_value(device->evdev,
						     slot->index,
						     ABS_MT_POSITION_X);
	slot->axes.point.y = libevdev_get_slot_value(device->evdev,
						     slot->index,
						     ABS_MT_POSITION_Y);
	slot->last_point.x = slot->axes.point.x;
	slot->last_point.y = slot->axes.point.y;
}

static enum totem_slot_state
totem_handle_slot_state(struct totem_dispatch *totem,
			struct totem_slot *slot,
			uint64_t time)
{
	struct evdev_device *device = totem->device;
	struct tablet_axes axes;
	enum libinput_tablet_tool_tip_state tip_state;
	bool updated;

	switch (slot->state) {
	case SLOT_STATE_BEGIN:
		if (!slot->tool)
			slot->tool = totem_new_tool(totem);
		slot_axes_initialize(totem, slot);
		totem_slot_mark_all_axes_changed(totem, slot, slot->tool);
		break;
	case SLOT_STATE_UPDATE:
	case SLOT_STATE_END:
		assert(slot->tool);
		break;
	case SLOT_STATE_NONE:
		return SLOT_STATE_NONE;
	}

	tip_state = LIBINPUT_TABLET_TOOL_TIP_UP;
	updated = totem_slot_fetch_axes(totem, slot, slot->tool, &axes, time);

	switch (slot->state) {
	case SLOT_STATE_BEGIN:
		tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN;
		tablet_notify_proximity(&device->base,
					time,
					slot->tool,
					LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN,
					slot->changed_axes,
					&axes);
		totem_slot_reset_changed_axes(totem, slot);
		tablet_notify_tip(&device->base,
				  time,
				  slot->tool,
				  tip_state,
				  slot->changed_axes,
				  &axes);
		slot->state = SLOT_STATE_UPDATE;
		break;
	case SLOT_STATE_UPDATE:
		tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN;
		if (updated) {
			tablet_notify_axis(&device->base,
					   time,
					   slot->tool,
					   tip_state,
					   slot->changed_axes,
					   &axes);
		}
		break;
	case SLOT_STATE_END:
		/* prox out is handled after button events */
		break;
	case SLOT_STATE_NONE:
		abort();
		break;
	}

	/* We only have one button but possibly multiple totems. It's not
	 * clear how the firmware will work, so for now we just handle the
	 * button state in the first slot.
	 *
	 * Due to the design of the totem we're also less fancy about
	 * button handling than the tablet code. Worst case, you might get
	 * tip up before button up but meh.
	 */
	if (totem->button_state_now != totem->button_state_previous) {
		enum libinput_button_state btn_state;

		if (totem->button_state_now)
			btn_state = LIBINPUT_BUTTON_STATE_PRESSED;
		else
			btn_state = LIBINPUT_BUTTON_STATE_RELEASED;

		tablet_notify_button(&device->base,
				     time,
				     slot->tool,
				     tip_state,
				     &axes,
				     BTN_0,
				     btn_state);

		totem->button_state_previous = totem->button_state_now;
	}

	switch(slot->state) {
	case SLOT_STATE_BEGIN:
	case SLOT_STATE_UPDATE:
		break;
	case SLOT_STATE_END:
		tip_state = LIBINPUT_TABLET_TOOL_TIP_UP;
		tablet_notify_tip(&device->base,
				  time,
				  slot->tool,
				  tip_state,
				  slot->changed_axes,
				  &axes);
		totem_slot_reset_changed_axes(totem, slot);
		tablet_notify_proximity(&device->base,
					time,
					slot->tool,
					LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT,
					slot->changed_axes,
					&axes);
		slot->state = SLOT_STATE_NONE;
		break;
	case SLOT_STATE_NONE:
		abort();
		break;
	}

	slot->last_point = slot->axes.point;
	totem_slot_reset_changed_axes(totem, slot);

	return slot->state;
}

static enum totem_slot_state
totem_handle_state(struct totem_dispatch *totem,
		   uint64_t time)
{
	enum totem_slot_state global_state = SLOT_STATE_NONE;

	for (size_t i = 0; i < totem->nslots; i++) {
		enum totem_slot_state s;

		s = totem_handle_slot_state(totem,
					    &totem->slots[i],
					    time);

		/* If one slot is active, the totem is active */
		if (s != SLOT_STATE_NONE)
			global_state = SLOT_STATE_UPDATE;
	}

	return global_state;
}

static void
totem_interface_process(struct evdev_dispatch *dispatch,
			struct evdev_device *device,
			struct input_event *e,
			uint64_t time)
{
	struct totem_dispatch *totem = totem_dispatch(dispatch);
	enum totem_slot_state global_state;
	bool enable_touch;

	switch(e->type) {
	case EV_ABS:
		totem_process_abs(totem, device, e, time);
		break;
	case EV_KEY:
		totem_process_key(totem, device, e, time);
		break;
	case EV_MSC:
		/* timestamp, ignore */
		break;
	case EV_SYN:
		global_state = totem_handle_state(totem, time);
		enable_touch = (global_state == SLOT_STATE_NONE);
		totem_set_touch_device_enabled(totem,
					       enable_touch,
					       time);
		break;
	default:
		evdev_log_error(device,
				"Unexpected event type %s (%#x)\n",
				libevdev_event_type_get_name(e->type),
				e->type);
		break;
	}
}

static void
totem_interface_suspend(struct evdev_dispatch *dispatch,
			struct evdev_device *device)
{
	struct totem_dispatch *totem = totem_dispatch(dispatch);
	uint64_t now = libinput_now(evdev_libinput_context(device));

	for (size_t i = 0; i < totem->nslots; i++) {
		struct totem_slot *slot = &totem->slots[i];
		struct tablet_axes axes;
		enum libinput_tablet_tool_tip_state tip_state;

		/* If we never initialized a tool, we can skip everything */
		if (!slot->tool)
			continue;

		totem_slot_fetch_axes(totem, slot, slot->tool, &axes, now);
		totem_slot_reset_changed_axes(totem, slot);

		if (slot->state == SLOT_STATE_NONE)
			tip_state = LIBINPUT_TABLET_TOOL_TIP_UP;
		else
			tip_state = LIBINPUT_TABLET_TOOL_TIP_DOWN;

		if (totem->button_state_now) {
			tablet_notify_button(&device->base,
					     now,
					     slot->tool,
					     tip_state,
					     &axes,
					     BTN_0,
					     LIBINPUT_BUTTON_STATE_RELEASED);

			totem->button_state_now = false;
			totem->button_state_previous = false;
		}

		if (slot->state != SLOT_STATE_NONE) {
			tablet_notify_tip(&device->base,
					  now,
					  slot->tool,
					  LIBINPUT_TABLET_TOOL_TIP_UP,
					  slot->changed_axes,
					  &axes);
		}
		tablet_notify_proximity(&device->base,
					now,
					slot->tool,
					LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_OUT,
					slot->changed_axes,
					&axes);
	}
	totem_set_touch_device_enabled(totem, true, now);
}

static void
totem_interface_destroy(struct evdev_dispatch *dispatch)
{
	struct totem_dispatch *totem = totem_dispatch(dispatch);

	free(totem->slots);
	free(totem);
}

static void
totem_interface_device_added(struct evdev_device *device,
			     struct evdev_device *added_device)
{
	struct totem_dispatch *totem = totem_dispatch(device->dispatch);
	struct libinput_device_group *g1, *g2;

	if ((evdev_device_get_id_vendor(added_device) !=
	    evdev_device_get_id_vendor(device)) ||
	    (evdev_device_get_id_product(added_device) !=
	     evdev_device_get_id_product(device)))
	    return;

	/* virtual devices don't have device groups, so check for that
	   libinput replay */
	g1 = libinput_device_get_device_group(&device->base);
	g2 = libinput_device_get_device_group(&added_device->base);
	if (g1 && g2 && g1->identifier != g2->identifier)
		return;

	if (totem->touch_device != NULL) {
		evdev_log_bug_libinput(device,
				       "already has a paired touch device, ignoring (%s)\n",
				       added_device->devname);
		return;
	}

	totem->touch_device = added_device;
	evdev_log_info(device, "%s: is the totem touch device\n", added_device->devname);
}

static void
totem_interface_device_removed(struct evdev_device *device,
			       struct evdev_device *removed_device)
{
	struct totem_dispatch *totem = totem_dispatch(device->dispatch);

	if (totem->touch_device != removed_device)
		return;

	totem_set_touch_device_enabled(totem, true,
				       libinput_now(evdev_libinput_context(device)));
	totem->touch_device = NULL;
}

static void
totem_interface_initial_proximity(struct evdev_device *device,
				  struct evdev_dispatch *dispatch)
{
	struct totem_dispatch *totem = totem_dispatch(dispatch);
	uint64_t now = libinput_now(evdev_libinput_context(device));
	bool enable_touch = true;

	for (size_t i = 0; i < totem->nslots; i++) {
		struct totem_slot *slot = &totem->slots[i];
		struct tablet_axes axes;
		int tracking_id;

		tracking_id = libevdev_get_slot_value(device->evdev,
						      i,
						      ABS_MT_TRACKING_ID);
		if (tracking_id == -1)
			continue;

		slot->tool = totem_new_tool(totem);
		slot_axes_initialize(totem, slot);
		totem_slot_mark_all_axes_changed(totem, slot, slot->tool);
		totem_slot_fetch_axes(totem, slot, slot->tool, &axes, now);
		tablet_notify_proximity(&device->base,
					now,
					slot->tool,
					LIBINPUT_TABLET_TOOL_PROXIMITY_STATE_IN,
					slot->changed_axes,
					&axes);
		totem_slot_reset_changed_axes(totem, slot);
		tablet_notify_tip(&device->base,
				  now,
				  slot->tool,
				  LIBINPUT_TABLET_TOOL_TIP_DOWN,
				  slot->changed_axes,
				  &axes);
		slot->state = SLOT_STATE_UPDATE;
		enable_touch = false;
	}

	totem_set_touch_device_enabled(totem, enable_touch, now);
}

struct evdev_dispatch_interface totem_interface = {
	.process = totem_interface_process,
	.suspend = totem_interface_suspend,
	.remove = NULL,
	.destroy = totem_interface_destroy,
	.device_added = totem_interface_device_added,
	.device_removed = totem_interface_device_removed,
	.device_suspended = totem_interface_device_added, /* treat as remove */
	.device_resumed = totem_interface_device_removed, /* treat as add */
	.post_added = totem_interface_initial_proximity,
	.touch_arbitration_toggle = NULL,
	.touch_arbitration_update_rect = NULL,
	.get_switch_state = NULL,
};

static bool
totem_reject_device(struct evdev_device *device)
{
	struct libevdev *evdev = device->evdev;
	bool has_xy, has_slot, has_tool_dial, has_size;
	double w, h;

	has_xy = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_X) &&
	         libevdev_has_event_code(evdev, EV_ABS, ABS_MT_POSITION_Y);
	has_slot = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_SLOT);
	has_tool_dial = libevdev_has_event_code(evdev, EV_ABS, ABS_MT_TOOL_TYPE) &&
			libevdev_get_abs_maximum(evdev, ABS_MT_TOOL_TYPE) >= MT_TOOL_DIAL;
	has_size = evdev_device_get_size(device, &w, &h) == 0;
	has_size |= libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MAJOR) > 0;
	has_size |= libevdev_get_abs_resolution(device->evdev, ABS_MT_TOUCH_MINOR) > 0;

	if (has_xy && has_slot && has_tool_dial && has_size)
		return false;

	evdev_log_bug_libinput(device,
			       "missing totem capabilities:%s%s%s%s. "
			       "Ignoring this device.\n",
			       has_xy ? "" : " xy",
			       has_slot ? "" : " slot",
			       has_tool_dial ? "" : " dial",
			       has_size ? "" : " resolutions");
	return true;
}

static uint32_t
totem_accel_config_get_profiles(struct libinput_device *libinput_device)
{
	return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
}

static enum libinput_config_status
totem_accel_config_set_profile(struct libinput_device *libinput_device,
			    enum libinput_config_accel_profile profile)
{
	return LIBINPUT_CONFIG_STATUS_UNSUPPORTED;
}

static enum libinput_config_accel_profile
totem_accel_config_get_profile(struct libinput_device *libinput_device)
{
	return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
}

static enum libinput_config_accel_profile
totem_accel_config_get_default_profile(struct libinput_device *libinput_device)
{
	return LIBINPUT_CONFIG_ACCEL_PROFILE_NONE;
}

static int
totem_init_accel(struct totem_dispatch *totem, struct evdev_device *device)
{
	const struct input_absinfo *x, *y;
	struct motion_filter *filter;

	x = device->abs.absinfo_x;
	y = device->abs.absinfo_y;

	/* same filter as the tablet */
	filter = create_pointer_accelerator_filter_tablet(x->resolution,
							  y->resolution);
	if (!filter)
		return -1;

	evdev_device_init_pointer_acceleration(device, filter);

	/* we override the profile hooks for accel configuration with hooks
	 * that don't allow selection of profiles */
	device->pointer.config.get_profiles = totem_accel_config_get_profiles;
	device->pointer.config.set_profile = totem_accel_config_set_profile;
	device->pointer.config.get_profile = totem_accel_config_get_profile;
	device->pointer.config.get_default_profile = totem_accel_config_get_default_profile;

	return 0;
}

struct evdev_dispatch *
evdev_totem_create(struct evdev_device *device)
{
	struct totem_dispatch *totem;
	struct totem_slot *slots;
	int num_slots;

	if (totem_reject_device(device))
		return NULL;

	totem = zalloc(sizeof *totem);
	totem->device = device;
	totem->base.dispatch_type = DISPATCH_TOTEM;
	totem->base.interface = &totem_interface;

	num_slots = libevdev_get_num_slots(device->evdev);
	if (num_slots <= 0)
		goto error;

	totem->slot = libevdev_get_current_slot(device->evdev);
	slots = zalloc(num_slots * sizeof(*totem->slots));

	for (int slot = 0; slot < num_slots; ++slot) {
		slots[slot].index = slot;
	}

	totem->slots = slots;
	totem->nslots = num_slots;

	evdev_init_sendevents(device, &totem->base);
	totem_init_accel(totem, device);

	return &totem->base;
error:
	totem_interface_destroy(&totem->base);
	return NULL;
}
