#define pr_fmt(fmt) "[hui][button] " fmt

#include "hui_internal.h"
#include "events.h"

#include <linux/timer.h>
#include <linux/slab.h>
#include <linux/spinlock.h>

#include <avm/event/event.h>

#include "button.h"
#include "log.h"

static inline void button_handle_timeout(timer_callback j);

static HUI_DEFINE_TIMER(button_timer, button_handle_timeout);

#define BUTTON_ACTIONS(...)      \
	(struct button_action[]) \
	{                        \
		__VA_ARGS__      \
		{                \
			0        \
		}                \
	}

#include "../def/button.c"

static struct kset *button_kset;

int avm_hui_send_button_event(unsigned int button_id, unsigned int pressed_time)
{
	struct _avm_event_push_button *event;

	event = kzalloc(sizeof(*event), GFP_ATOMIC);

	if (!event)
		return -ENOMEM;

	event->id = avm_event_id_push_button;
	event->key = button_id;
	event->pressed = pressed_time;

	hui_log_button_event(button_id, pressed_time);
	avm_event_source_trigger(event_source, avm_event_id_push_button,
				 sizeof(*event), event);

	return 0;
}

static inline const char *button_event_to_string(enum button_event event)
{
	switch (event) {
	case button_event_none:
		return "none";
	case button_event_down:
		return "down";
	case button_event_up:
		return "up";
	case button_event_cancelled:
		return "cancelled";
	}

	return NULL;
}

/*!
 * Counts all buttons currently in an action similar to the specified.
 */
static inline int button_multi_count(const struct button_action *action)
{
	struct button *button;
	int count = 0;

	list_for_each_entry (button, &button_kset->list, kobj.entry) {
		const struct button_action *a;

		// Not pressed
		if (button->current_action == -1)
			continue;

		a = button->actions + button->current_action;

		// Not a multi-button
		if (!(a->flags & BUTTON_FLAG_MULTI))
			continue;

		// Actions are not the same
		if (a->ms != action->ms || a->down != action->down ||
		    a->up != action->up || a->flags != action->flags)
			continue;

		// Found matching action
		count++;
	}

	return count;
}

/*!
 * The default handler for button actions.
 *
 * It handles two factor authentication and multi button presses.
 *
 * As final result it sends a button event.
 */
static inline void button_default_handler(struct button *button,
					  const struct button_action *action,
					  enum button_event event,
					  unsigned long elapsed)
{
	if (event == button_event_cancelled)
		return;

	// Any press should trigger a two factor press
	if (event_get_value(two_factor) == two_factor_start) {
		avm_hui_send_button_event(avm_event_push_button_2fa_success, elapsed);
		avm_hui_send_event(event_two_factor_success, 1);

		// Mark this button to be done for now
		button->jiffies_down = MAX_JIFFY_OFFSET;
		return;
	}

	if (event_get_value(button_events) == button_events_disable &&
	    (action->flags & BUTTON_FLAG_IGNORE_LOCK) == 0) {
		pr_info("Discard disabled button event: %s, %d, %s\n",
			kobject_name(&button->kobj), action->ms,
			button_event_to_string(event));

		return;
	}

	// Require at least 2 pressed buttons (including this one)
	if ((action->flags & BUTTON_FLAG_MULTI) &&
	    button_multi_count(action) < 2) {
		pr_info("Ignore mutli event: %s, %d, %s\n",
			kobject_name(&button->kobj), action->ms,
			button_event_to_string(event));
		return;
	}

	if (event == button_event_down && action->down) {
		/*
		 * Zero here denotes that the button is just pressed and may be
		 * cancelled. The cancellation is not signalled.
		 */
		avm_hui_send_button_event(action->down, 0);
	} else if (event == button_event_up && action->up) {
		avm_hui_send_button_event(action->up, elapsed);
	}
}

int button_init(void)
{
	int i, j;

	for (i = 0; i < ARRAY_SIZE(button_defs); i++) {
		struct button_def *def = button_defs + i;

		for (j = 0; def->actions[j].ms != 0; j++) {
			struct button_action *action = def->actions + j;

			if (!action->handler)
				action->handler = button_default_handler;
		}
	}

	button_kset = kset_create_and_add("button", NULL, &hui_kobj->obj.kobj);
	if (!button_kset)
		return -ENOMEM;

	return 0;
}

void button_exit(void)
{
	del_timer_sync(&button_timer);
}

static inline struct button_def *lookup_button_def(const char *name)
{
	int i;

	for (i = 0; i < ARRAY_SIZE(button_defs); i++) {
		struct button_def *def = button_defs + i;

		if (strcmp(def->name, name) == 0)
			return def;
	}

	return NULL;
}

static inline void deliver_button_event(struct button *button,
					enum button_event event);

/*
 * This function looks the currently active actions
 * and calculates when it should transition to the next action.
 *
 * The lowest timeout is then used for the button timer.
 *
 * The list lock must be held before calling this function. It does
 * not depend on the current time.
 */
static inline void update_button_timer(void)
{
	struct button *button;
	unsigned long timeout = MAX_JIFFY_OFFSET;

	list_for_each_entry (button, &button_kset->list, kobj.entry) {
		const struct button_action *next;
		unsigned long b_timeout;

		/* Button not pressed  */
		if (button->jiffies_down == MAX_JIFFY_OFFSET)
			continue;

		next = &button->actions[button->current_action + 1];

		/* End marker for the action list */
		if (next->ms == 0)
			continue;

		b_timeout = button->jiffies_down + msecs_to_jiffies(next->ms);

		if (time_after(timeout, b_timeout))
			timeout = b_timeout;
	}

	if (timeout != MAX_JIFFY_OFFSET)
		mod_timer(&button_timer, timeout);
}

static inline void button_handle_timeout(timer_callback j)
{
	struct button *button;

	spin_lock(&button_kset->list_lock);

	list_for_each_entry (button, &button_kset->list, kobj.entry) {
		if (button->jiffies_down != MAX_JIFFY_OFFSET)
			deliver_button_event(button, button_event_none);
	}
	update_button_timer();

	spin_unlock(&button_kset->list_lock);
}

/*
 * This function used the current time to calculate in which action the button
 * should be in.
 *
 * If the calculate action is different from the current action, it will call
 * the handlers to let them know and change the current action. Otherwise
 * nothing happens.
 *
 * If the last action is reached, the up event is triggered immediately.
 */
static inline void deliver_button_event(struct button *button,
					enum button_event event)
{
	int elapsed = jiffies_to_msecs(jiffies - button->jiffies_down);
	int i, new_action = button->current_action;

	pr_debug("Handle button %s: %s (elapsed=%d)\n", button_name(button),
		 button_event_to_string(event), elapsed);

	// Find the action we should be in
	for (i = button->current_action + 1; button->actions[i].ms != 0; i++) {
		const struct button_action *action = button->actions + i;

		if (elapsed >= action->ms) {
			new_action = i;
		}
	}

	// Notify old handler that it has been cancelled, if we changed it
	if (button->current_action != new_action &&
	    button->current_action != -1) {
		const struct button_action *action =
			button->actions + button->current_action;

		action->handler(button, action, button_event_cancelled,
				elapsed);
	}

	button->current_action = new_action;
	if (button->current_action != -1) {
		const struct button_action *action =
			button->actions + button->current_action;

		// A timer triggered us, so fake a down
		if (event == button_event_none)
			action->handler(button, action, button_event_down,
					elapsed);
		else
			action->handler(button, action, event, elapsed);
	}

	// In last action, immediately trigger up event
	if (button->actions[button->current_action + 1].ms == 0) {
		const struct button_action *action =
			button->actions + button->current_action;

		action->handler(button, action, button_event_up, elapsed);

		// Reset, so we don't trigger up twice
		button->jiffies_down = MAX_JIFFY_OFFSET;
		// Do not reset current_action, as others may want to inspect
		// were we are at
	}
}

void *avm_hui_add_button(const char *name, const char *type)
{
	struct button_def *def;
	struct button *button;
	int err;

	def = lookup_button_def(type);
	if (!def)
		return ERR_PTR(-ENOENT);

	button = kzalloc(sizeof(struct button), GFP_KERNEL);
	if (!button)
		return ERR_PTR(-ENOMEM);

	button->kobj.kset = button_kset;
	button->actions = def->actions;
	button->current_action = -1;
	button->jiffies_down = MAX_JIFFY_OFFSET;

	err = kobject_init_and_add(&button->kobj, &button_ktype, NULL, "%s",
				   name);
	if (err)
		return ERR_PTR(err);

	return button;
}
EXPORT_SYMBOL(avm_hui_add_button);

void avm_hui_remove_button(void *handle)
{
	struct button *button = handle;

	if (!button || IS_ERR(button))
		return;

	kobject_put(&button->kobj);
}
EXPORT_SYMBOL(avm_hui_remove_button);

void avm_hui_button_input(void *handle, int pressed)
{
	struct button *button = handle;

	spin_lock(&button_kset->list_lock);
	if (pressed) {
		hui_log_button(button, button_event_down);

		if (button->jiffies_down != MAX_JIFFY_OFFSET) {
			pr_err("Ignore duplicated down for %s\n",
			       button_name(button));
			spin_unlock(&button_kset->list_lock);
			return;
		}

		/*
		 * Temporarily enable led display
		 */
		avm_hui_send_event(event_display_suspend_on_idle, 1);

		button->jiffies_down = jiffies;
		deliver_button_event(button, button_event_down);
	} else {
		hui_log_button(button, button_event_up);

		if (button->jiffies_down == MAX_JIFFY_OFFSET) {
			pr_err("Ignore duplicated up for %s\n",
			       button_name(button));
			button->current_action = -1;
			spin_unlock(&button_kset->list_lock);
			return;
		}

		deliver_button_event(button, button_event_up);
		button->jiffies_down = MAX_JIFFY_OFFSET;
		button->current_action = -1;
	}
	update_button_timer();
	spin_unlock(&button_kset->list_lock);
}
EXPORT_SYMBOL(avm_hui_button_input);