#include <linux/input-event-codes.h>
#include <linux/input.h>
#include <time.h>
#include "input.h"
#include "input_internal.h"
#include "shl/eloop.h"
#include "shl/hook.h"

static void pointer_update_inactivity_timer(struct input_dev *dev)
{
	struct itimerspec spec;

	spec.it_interval.tv_nsec = 0;
	spec.it_interval.tv_sec = 0;
	spec.it_value.tv_nsec = 0;
	spec.it_value.tv_sec = 20;
	ev_timer_update(dev->input->hide_pointer, &spec);
}

static void pointer_dev_send_move(struct input_dev *dev)
{
	struct input_pointer_event pev = {0};

	pev.event = POINTER_MOVED;
	pev.pointer_x = dev->pointer.x;
	pev.pointer_y = dev->pointer.y;

	/* Include button state if a button is pressed during motion (drag) */
	if (dev->pointer.pressed_button != BUTTON_NONE) {
		pev.button = dev->pointer.pressed_button;
		pev.pressed = true;
	} else {
		pev.button = 0;
		pev.pressed = false;
	}

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

static void pointer_dev_send_wheel(struct input_dev *dev, int32_t value)
{
	struct input_pointer_event pev = {0};

	pev.event = POINTER_WHEEL;
	pev.wheel = value;

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

static void pointer_dev_send_button(struct input_dev *dev, uint8_t button, bool pressed,
				    bool dbl_click)
{
	struct input_pointer_event pev = {0};

	pev.event = POINTER_BUTTON;
	pev.button = button;
	pev.pressed = pressed;
	pev.double_click = dbl_click;

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

void pointer_dev_sync(struct input_dev *dev)
{
	struct input_pointer_event pev = {0};

	pev.event = POINTER_SYNC;

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

void pointer_dev_rel(struct input_dev *dev, uint16_t code, int32_t value)
{
	switch (code) {
	case REL_X:
		dev->pointer.x += value;
		if (dev->pointer.x < 0)
			dev->pointer.x = 0;
		if (dev->pointer.x > dev->input->pointer_max_x)
			dev->pointer.x = dev->input->pointer_max_x;
		pointer_dev_send_move(dev);
		break;
	case REL_Y:
		dev->pointer.y += value;
		if (dev->pointer.y < 0)
			dev->pointer.y = 0;
		if (dev->pointer.y > dev->input->pointer_max_y)
			dev->pointer.y = dev->input->pointer_max_y;
		pointer_dev_send_move(dev);
		break;
	case REL_WHEEL:
		pointer_dev_send_wheel(dev, value);
		break;
	default:
		break;
	}
}

static void pointer_dev_abs_x(struct input_dev *dev, int32_t value)
{
	switch (dev->pointer.kind) {
	case POINTER_TOUCHPAD:
		if (dev->pointer.touchpad_needs_sync_off_x) {
			dev->pointer.off_x = dev->pointer.x - value;
			dev->pointer.touchpad_needs_sync_off_x = false;
		}

		if (dev->pointer.touchpaddown) {
			dev->pointer.x = dev->pointer.off_x + value;
			if (dev->pointer.x < 0) {
				dev->pointer.x = 0;
				dev->pointer.off_x = -value;
			}
			if (dev->pointer.x > dev->input->pointer_max_x) {
				dev->pointer.x = dev->input->pointer_max_x;
				dev->pointer.off_x = dev->input->pointer_max_x - value;
			}
		}
		break;
	case POINTER_TOUCHSCREEN:
	case POINTER_VMOUSE:
		dev->pointer.x = ((value - dev->pointer.min_x) * dev->input->pointer_max_x) /
				 (dev->pointer.max_x - dev->pointer.min_x);
		break;
	default:
		return;
	}
	pointer_dev_send_move(dev);
}

static void pointer_dev_abs_y(struct input_dev *dev, int32_t value)
{
	switch (dev->pointer.kind) {
	case POINTER_TOUCHPAD:
		if (dev->pointer.touchpad_needs_sync_off_y) {
			dev->pointer.off_y = dev->pointer.y - value;
			dev->pointer.touchpad_needs_sync_off_y = false;
		}

		if (dev->pointer.touchpaddown) {
			dev->pointer.y = dev->pointer.off_y + value;
			if (dev->pointer.y < 0) {
				dev->pointer.y = 0;
				dev->pointer.off_y = -value;
			}
			if (dev->pointer.y > dev->input->pointer_max_y) {
				dev->pointer.y = dev->input->pointer_max_y;
				dev->pointer.off_y = dev->input->pointer_max_y - value;
			}
		}
		break;
	case POINTER_TOUCHSCREEN:
	case POINTER_VMOUSE:
		dev->pointer.y = ((value - dev->pointer.min_y) * dev->input->pointer_max_y) /
				 (dev->pointer.max_y - dev->pointer.min_y);
		break;
	default:
		return;
	}
	pointer_dev_send_move(dev);
}

void pointer_dev_abs(struct input_dev *dev, uint16_t code, int32_t value)
{
	switch (code) {
	case ABS_X:
		pointer_dev_abs_x(dev, value);
		break;
	case ABS_Y:
		pointer_dev_abs_y(dev, value);
		break;
	}
}

void pointer_dev_button(struct input_dev *dev, uint16_t code, int32_t value)
{
	struct timespec tp;
	uint64_t elapsed;
	bool pressed = (value == 1);
	bool dbl_click = false;

	switch (code) {
	case BTN_LEFT:
		if (pressed) {
			clock_gettime(CLOCK_MONOTONIC, &tp);
			elapsed = (tp.tv_sec - dev->pointer.last_click.tv_sec) * 1000 +
				  (tp.tv_nsec - dev->pointer.last_click.tv_nsec) / 1000000;
			dbl_click = (elapsed < 500);
			dev->pointer.last_click = tp;
			dev->pointer.pressed_button = 0; /* Button 0 = left */
		} else {
			dev->pointer.pressed_button = BUTTON_NONE; /* No button */
		}
		pointer_dev_send_button(dev, 0, pressed, dbl_click);
		break;
	case BTN_RIGHT:
		dev->pointer.pressed_button = pressed ? 1 : BUTTON_NONE; /* Button 1 = right */
		pointer_dev_send_button(dev, 1, pressed, false);
		break;
	case BTN_TOOL_DOUBLETAP:
	case BTN_TOOL_TRIPLETAP:
	case BTN_MIDDLE:
		dev->pointer.pressed_button = pressed ? 2 : BUTTON_NONE; /* Button 2 = middle */
		pointer_dev_send_button(dev, 2, pressed, false);
		break;
	case BTN_TOUCH:
		dev->pointer.touchpaddown = pressed;
		if (pressed) {
			dev->pointer.touchpad_needs_sync_off_x = true;
			dev->pointer.touchpad_needs_sync_off_y = true;
		}
		break;
	default:
		break;
	}
}
