/*
 * Kmscon - Input Handling
 *
 * Copyright (c) 2011 Ran Benita <ran234@gmail.com>
 * Copyright (c) 2011-2012 David Herrmann <dh.herrmann@googlemail.com>
 *
 * 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 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.
 */

/*
 * Input Devices
 */

#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <linux/input-event-codes.h>
#include <linux/input.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include "input.h"
#include "input_internal.h"
#include "shl/dlist.h"
#include "shl/eloop.h"
#include "shl/hook.h"
#include "shl/log.h"
#include "shl/misc.h"

#define LOG_SUBSYSTEM "input"

/* How many longs are needed to hold \n bits. */
#define NLONGS(n) (((n) + LONG_BIT - 1) / LONG_BIT)

static void input_free_dev(struct input_dev *dev);

static void notify_event(struct input_dev *dev, uint16_t type, uint16_t code, int32_t value)
{
	switch (type) {
	case EV_KEY:
		if (dev->capabilities & DEVICE_HAS_KEYS)
			uxkb_dev_process(dev, value, code);
		pointer_dev_button(dev, code, value);
		break;
	case EV_REL:
		pointer_dev_rel(dev, code, value);
		break;
	case EV_ABS:
		pointer_dev_abs(dev, code, value);
		break;
	case EV_SYN:
		pointer_dev_sync(dev);
		break;
	}
}

static void input_data_dev(struct ev_fd *fd, int mask, void *data)
{
	struct input_dev *dev = data;
	struct input_event ev[16];
	ssize_t len, n;
	int i;

	if (mask & (EV_HUP | EV_ERR)) {
		log_debug("EOF on %s", dev->node);
		input_free_dev(dev);
		return;
	}

	len = sizeof(ev);
	while (len == sizeof(ev)) {
		len = read(dev->rfd, &ev, sizeof(ev));
		if (len < 0) {
			if (errno == EWOULDBLOCK)
				break;
			log_warn("reading from %s failed (%d): %m", dev->node, errno);
			input_free_dev(dev);
		} else if (len == 0) {
			log_debug("EOF on %s", dev->node);
			input_free_dev(dev);
		} else if (len % sizeof(*ev)) {
			log_warn("invalid input_event on %s", dev->node);
		} else {
			n = len / sizeof(*ev);
			for (i = 0; i < n; i++)
				notify_event(dev, ev[i].type, ev[i].code, ev[i].value);
		}
	}
}

static int input_init_abs(struct input_dev *dev)
{
	struct input_absinfo info;
	int ret;

	if (dev->rfd <= 0)
		return -EFAULT;
	ret = ioctl(dev->rfd, EVIOCGABS(ABS_X), &info);
	if (ret < 0)
		return ret;

	dev->pointer.min_x = info.minimum;
	dev->pointer.max_x = info.maximum;

	ret = ioctl(dev->rfd, EVIOCGABS(ABS_Y), &info);
	if (ret < 0)
		return ret;

	dev->pointer.min_y = info.minimum;
	dev->pointer.max_y = info.maximum;

	log_debug("ABSX min %d max %d ABSY min %d max %d\n", dev->pointer.min_x, dev->pointer.max_x,
		  dev->pointer.min_y, dev->pointer.max_y);
	return ret;
}

static void input_close_dev(struct input_dev *dev)
{
	if (dev->input->close_cb)
		dev->input->close_cb(dev->rfd, dev->fd_id, dev->input->data);
	else
		close(dev->rfd);

	dev->rfd = -1;
	dev->fd_id = -1;
}

static int input_open_dev(struct input_dev *dev)
{
	int ret;

	if (dev->input->open_cb)
		dev->rfd = dev->input->open_cb(dev->node, &dev->fd_id, dev->input->data);
	else
		dev->rfd = open(dev->node, O_CLOEXEC | O_NONBLOCK | O_RDWR);

	if (dev->rfd < 0) {
		log_warn("cannot open device %s (%d): %m", dev->node, errno);
		return -EFAULT;
	}

	if (dev->capabilities & DEVICE_HAS_ABS)
		input_init_abs(dev);

	ret = ev_eloop_new_fd(dev->input->eloop, &dev->fd, dev->rfd, EV_READABLE, input_data_dev,
			      dev);
	if (ret)
		input_close_dev(dev);
	return ret;
}

static int input_wake_up_dev(struct input_dev *dev)
{
	int ret;

	if (dev->rfd >= 0)
		return 0;

	ret = input_open_dev(dev);
	if (ret)
		return ret;

	if (dev->capabilities & DEVICE_HAS_KEYS)
		uxkb_dev_wake_up(dev);

	return 0;
}

static void input_sleep_dev(struct input_dev *dev)
{
	if (dev->rfd < 0 || !dev->initialized)
		return;

	dev->repeating = false;
	ev_timer_update(dev->repeat_timer, NULL);
	ev_eloop_rm_fd(dev->fd);
	dev->fd = NULL;
	input_close_dev(dev);
}

static int input_init_keyboard(struct input_dev *dev)
{
	dev->num_syms = 1;
	dev->event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms);
	if (!dev->event.keysyms)
		return -1;
	dev->event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms);
	if (!dev->event.codepoints)
		goto err_syms;
	dev->repeat_event.keysyms = malloc(sizeof(uint32_t) * dev->num_syms);
	if (!dev->repeat_event.keysyms)
		goto err_codepoints;
	dev->repeat_event.codepoints = malloc(sizeof(uint32_t) * dev->num_syms);
	if (!dev->repeat_event.codepoints)
		goto err_rsyms;

	if (uxkb_dev_init(dev))
		goto err_rcodepoints;

	return 0;

err_rcodepoints:
	free(dev->repeat_event.codepoints);
err_rsyms:
	free(dev->repeat_event.keysyms);
err_codepoints:
	free(dev->event.codepoints);
err_syms:
	free(dev->event.keysyms);
	return -1;
}

static void input_exit_keyboard(struct input_dev *dev)
{
	uxkb_dev_destroy(dev);
	free(dev->repeat_event.codepoints);
	free(dev->repeat_event.keysyms);
	free(dev->event.codepoints);
	free(dev->event.keysyms);
}

static const char *pointer_kind_name(enum pointer_kind kind)
{
	switch (kind) {
	default:
	case POINTER_NONE:
		return "";
	case POINTER_TOUCHPAD:
		return "Touchpad";
	case POINTER_MOUSE:
		return "Mouse";
	case POINTER_TOUCHSCREEN:
		return "Touchscreen";
	case POINTER_VMOUSE:
		return "Virtual mouse";
	}
}

static enum pointer_kind input_pointer_kind(unsigned int capabilities)
{
	if (capabilities & DEVICE_HAS_ABS) {
		if (capabilities & DEVICE_HAS_TOUCH) {
			if (capabilities & DEVICE_HAS_DIRECT)
				return POINTER_TOUCHSCREEN;
			return POINTER_TOUCHPAD;
		}
		return POINTER_VMOUSE;
	}
	if (capabilities & DEVICE_HAS_REL)
		return POINTER_MOUSE;
	return POINTER_NONE;
}

/*
 * See if the device has anything useful to offer.
 * We go over the possible capabilities and return a mask of enum
 * input_device_capability's.
 */
static unsigned int probe_device_capabilities(int fd, struct input *input, const char *name)
{
	int i, ret;
	unsigned int capabilities = 0;
	unsigned long evbits[NLONGS(EV_CNT)] = {0};
	unsigned long keybits[NLONGS(KEY_CNT)] = {0};
	unsigned long relbits[NLONGS(REL_CNT)] = {0};
	unsigned long absbits[NLONGS(ABS_CNT)] = {0};
	unsigned long props[NLONGS(INPUT_PROP_CNT)] = {0};

	/* Which types of input events the device supports. */
	ret = ioctl(fd, EVIOCGBIT(0, sizeof(evbits)), evbits);
	if (ret == -1)
		goto err_ioctl;

	/* Device supports keys/buttons. */
	if (input_bit_is_set(evbits, EV_KEY)) {
		ret = ioctl(fd, EVIOCGBIT(EV_KEY, sizeof(keybits)), keybits);
		if (ret == -1)
			goto err_ioctl;

		/*
		 * If the device support any of the normal keyboard keys, we
		 * take it. Even if the keys are not ordinary they can be
		 * mapped to anything by the keyboard backend.
		 */
		for (i = KEY_RESERVED; i <= KEY_MIN_INTERESTING; i++) {
			if (input_bit_is_set(keybits, i)) {
				capabilities |= DEVICE_HAS_KEYS;
				break;
			}
		}
		if (input_bit_is_set(keybits, BTN_LEFT))
			capabilities |= DEVICE_HAS_MOUSE_BTN;
		if (input_bit_is_set(keybits, BTN_TOUCH))
			capabilities |= DEVICE_HAS_TOUCH;
	}

	if (input_bit_is_set(evbits, EV_SYN) && input_bit_is_set(evbits, EV_REL)) {
		ret = ioctl(fd, EVIOCGBIT(EV_REL, sizeof(relbits)), relbits);
		if (ret < 0)
			goto err_ioctl;
		if (input_bit_is_set(relbits, REL_X) && input_bit_is_set(relbits, REL_Y))
			capabilities |= DEVICE_HAS_REL;
		if (input_bit_is_set(relbits, REL_WHEEL))
			capabilities |= DEVICE_HAS_WHEEL;
	}

	if (input_bit_is_set(evbits, EV_SYN) && input_bit_is_set(evbits, EV_ABS)) {
		ret = ioctl(fd, EVIOCGBIT(EV_ABS, sizeof(absbits)), absbits);
		if (ret < 0)
			goto err_ioctl;
		if (input_bit_is_set(absbits, ABS_X) && input_bit_is_set(absbits, ABS_Y))
			capabilities |= DEVICE_HAS_ABS;

		ret = ioctl(fd, EVIOCGPROP(sizeof(props)), props);
		if (ret < 0)
			goto err_ioctl;
		if (input_bit_is_set(props, INPUT_PROP_DIRECT))
			capabilities |= DEVICE_HAS_DIRECT;
	}

	if (input_bit_is_set(evbits, EV_LED))
		capabilities |= DEVICE_HAS_LEDS;

	return capabilities;

err_ioctl:
	log_warn("cannot probe capabilities of device %s (%d): %m", name, errno);
	return 0;
}

#define HAS_ALL(caps, flags) (((caps) & (flags)) == (flags))

/*
 * Probe a device via an already-opened fd and add it to the active device list
 * if it has useful capabilities.
 */
static bool input_can_use(struct input *input, unsigned int capabilities)
{

	if (HAS_ALL(capabilities, DEVICE_HAS_KEYS))
		return true;

	if (HAS_ALL(capabilities, DEVICE_HAS_REL | DEVICE_HAS_MOUSE_BTN) ||
	    HAS_ALL(capabilities, DEVICE_HAS_ABS | DEVICE_HAS_TOUCH) ||
	    HAS_ALL(capabilities, DEVICE_HAS_ABS | DEVICE_HAS_MOUSE_BTN))
		if (input->mouse_enabled)
			return true;
	return false;
}

static void input_free_dev(struct input_dev *dev)
{
	log_debug("free device %s", dev->node);
	input_sleep_dev(dev);
	shl_dlist_unlink(&dev->list);
	if (dev->capabilities & DEVICE_HAS_KEYS)
		input_exit_keyboard(dev);
	free(dev->node);
	free(dev);
}

static int input_init_dev(struct input *input, struct input_dev *dev)
{
	char name[64] = {0};
	int ret;

	ret = input_open_dev(dev);
	if (ret)
		goto err;

	ioctl(dev->rfd, EVIOCGNAME(sizeof(name) - 1), name);
	dev->capabilities = probe_device_capabilities(dev->rfd, input, name);
	if (!input_can_use(input, dev->capabilities)) {
		ret = -ENOTSUP;
		log_debug("ignoring non-useful device %s [%s]", dev->node, name);
		goto err_close;
	}

	dev->pointer.pressed_button = BUTTON_NONE; /* No button pressed initially */

	if (dev->capabilities & DEVICE_HAS_KEYS) {
		ret = input_init_keyboard(dev);
		if (ret)
			goto err_close;
	}
	dev->pointer.kind = input_pointer_kind(dev->capabilities);
	if (dev->capabilities & DEVICE_HAS_ABS)
		input_init_abs(dev);

	dev->initialized = true;

	log_info("Using device %s [%s] as %s%s\n", dev->node, name,
		 (dev->capabilities & DEVICE_HAS_KEYS) ? "keyboard" : "",
		 pointer_kind_name(dev->pointer.kind));

	return 0;

err_close:
	ev_eloop_rm_fd(dev->fd);
	input_close_dev(dev);
err:
	shl_dlist_unlink(&dev->list);
	free(dev->node);
	free(dev);
	return ret;
}

static struct input_dev *input_new_dev(struct input *input, const char *node)
{
	struct input_dev *dev;

	dev = malloc(sizeof(*dev));
	if (!dev)
		return NULL;
	memset(dev, 0, sizeof(*dev));
	dev->input = input;
	dev->rfd = -1;

	dev->node = strdup(node);
	if (!dev->node)
		goto err_free;

	shl_dlist_link(&input->devices, &dev->list);
	return dev;

err_free:
	free(dev);
	return NULL;
}

static void hide_pointer_timer(struct ev_timer *timer, uint64_t num, void *data)
{
	struct input *input = data;
	struct input_pointer_event pev;

	pev.event = POINTER_HIDE_TIMEOUT;

	shl_hook_call(input->pointer_hook, input, &pev);
}

SHL_EXPORT
int input_new(struct input **out, struct ev_eloop *eloop)
{
	struct input *input;
	int ret;

	if (!out || !eloop)
		return -EINVAL;

	input = malloc(sizeof(*input));
	if (!input)
		return -ENOMEM;
	memset(input, 0, sizeof(*input));
	input->ref = 1;
	input->eloop = eloop;
	shl_dlist_init(&input->devices);

	ret = shl_hook_new(&input->key_hook);
	if (ret)
		goto err_free;

	ret = shl_hook_new(&input->pointer_hook);
	if (ret)
		goto err_hook;

	ret = ev_eloop_new_timer(input->eloop, &input->hide_pointer, NULL, hide_pointer_timer,
				 input);
	if (ret)
		goto err_hook_pointer;

	log_debug("new input %p", input);
	ev_eloop_ref(input->eloop);
	*out = input;
	return 0;

err_hook_pointer:
	shl_hook_free(input->pointer_hook);

err_hook:
	shl_hook_free(input->key_hook);

err_free:
	free(input);
	return ret;
}

SHL_EXPORT
int input_set_keymap(struct input *input, const char *model, const char *layout,
		     const char *variant, const char *options, const char *locale,
		     const char *keymap, const char *compose_file, size_t compose_file_len)
{
	/* xkbcommon won't use the XKB_DEFAULT_OPTIONS environment
	 * variable if options is an empty string.
	 * So if all variables are empty, use NULL instead.
	 */
	if (model && *model == 0 && layout && *layout == 0 && variant && *variant == 0 && options &&
	    *options == 0) {
		model = NULL;
		layout = NULL;
		variant = NULL;
		options = NULL;
	}

	return uxkb_desc_init(input, model, layout, variant, options, locale, keymap, compose_file,
			      compose_file_len);
}

SHL_EXPORT
void input_set_conf(struct input *input, unsigned int repeat_delay, unsigned int repeat_rate,
		    bool mouse_enabled)
{
	if (!repeat_delay)
		repeat_delay = 250;
	if (repeat_delay >= 1000)
		repeat_delay = 999;
	if (!repeat_rate)
		repeat_rate = 50;
	if (repeat_rate >= 1000)
		repeat_rate = 999;

	input->repeat_delay = repeat_delay;
	input->repeat_rate = repeat_rate;
	input->mouse_enabled = mouse_enabled;
}

SHL_EXPORT
void input_ref(struct input *input)
{
	if (!input || !input->ref)
		return;

	++input->ref;
}

SHL_EXPORT
void input_unref(struct input *input)
{
	struct input_dev *dev;

	if (!input || !input->ref || --input->ref)
		return;

	log_debug("free object %p", input);

	while (input->devices.next != &input->devices) {
		dev = shl_dlist_entry(input->devices.next, struct input_dev, list);
		input_free_dev(dev);
	}

	uxkb_desc_destroy(input);
	shl_hook_free(input->key_hook);
	shl_hook_free(input->pointer_hook);
	ev_eloop_rm_timer(input->hide_pointer);
	ev_eloop_unref(input->eloop);
	free(input);
}

SHL_EXPORT
void *input_add_dev(struct input *input, const char *node)
{
	struct input_dev *dev;

	if (!input || !node)
		return NULL;

	dev = input_new_dev(input, node);
	if (!dev)
		return NULL;

	if (input->awake > 0) {
		if (input_init_dev(input, dev))
			return NULL;
	}
	return dev;
}

SHL_EXPORT
void input_remove_dev(struct input *input, void *data)
{
	struct shl_dlist *iter;
	struct input_dev *dev;

	if (!input || !data)
		return;

	shl_dlist_for_each(iter, &input->devices)
	{
		dev = shl_dlist_entry(iter, struct input_dev, list);
		if (dev == data) {
			input_free_dev(dev);
			return;
		}
	}
}

SHL_EXPORT
int input_register_key_cb(struct input *input, input_key_cb cb, void *data)
{
	if (!input || !cb)
		return -EINVAL;

	return shl_hook_add_cast(input->key_hook, cb, data, false);
}

SHL_EXPORT
void input_unregister_key_cb(struct input *input, input_key_cb cb, void *data)
{
	if (!input || !cb)
		return;

	shl_hook_rm_cast(input->key_hook, cb, data);
}

SHL_EXPORT
int input_register_pointer_cb(struct input *input, input_pointer_cb cb, void *data)
{
	if (!input || !cb)
		return -EINVAL;

	return shl_hook_add_cast(input->pointer_hook, cb, data, false);
}

SHL_EXPORT
void input_unregister_pointer_cb(struct input *input, input_pointer_cb cb, void *data)
{
	if (!input || !cb)
		return;

	shl_hook_rm_cast(input->pointer_hook, cb, data);
}

SHL_EXPORT
void input_set_device_ops(struct input *input, uterm_open_cb open_cb, uterm_close_cb close_cb,
			  void *data)
{
	if (!input)
		return;

	input->open_cb = open_cb;
	input->close_cb = close_cb;
	input->data = data;
}

SHL_EXPORT
void input_sleep(struct input *input)
{
	struct shl_dlist *iter;
	struct input_dev *dev;

	if (!input)
		return;

	--input->awake;
	if (input->awake != 0)
		return;

	log_debug("going to sleep");

	shl_dlist_for_each(iter, &input->devices)
	{
		dev = shl_dlist_entry(iter, struct input_dev, list);
		input_sleep_dev(dev);
	}
}

SHL_EXPORT
void input_wake_up(struct input *input)
{
	struct shl_dlist *iter, *tmp;
	struct input_dev *dev;
	int ret;

	if (!input)
		return;

	++input->awake;
	if (input->awake != 1)
		return;

	log_debug("waking up");

	/* Wake up already-probed devices */
	shl_dlist_for_each_safe(iter, tmp, &input->devices)
	{
		dev = shl_dlist_entry(iter, struct input_dev, list);

		if (!dev->initialized) {
			ret = input_init_dev(input, dev);
			if (ret)
				continue;
		}

		ret = input_wake_up_dev(dev);
		if (ret)
			input_free_dev(dev);
	}
}

SHL_EXPORT
bool input_is_awake(struct input *input)
{
	if (!input)
		return false;

	return input->awake > 0;
}

SHL_EXPORT
void input_set_pointer_max(struct input *input, unsigned int max_x, unsigned int max_y)
{
	if (!input)
		return;

	input->pointer_max_x = max_x;
	input->pointer_max_y = max_y;
}

SHL_EXPORT
void input_set_leds(struct input *input, unsigned int scroll_lock, unsigned int num_lock,
		    unsigned int caps_lock)
{
	struct shl_dlist *iter;
	struct input_dev *dev;

	if (!input)
		return;

	shl_dlist_for_each(iter, &input->devices)
	{
		dev = shl_dlist_entry(iter, struct input_dev, list);
		uxkb_dev_set_leds(dev, scroll_lock, num_lock, caps_lock);
	}
}
